mirror of
https://github.com/SomboChea/ui
synced 2024-11-28 00:44:30 +07:00
fix: refactor/116 RegistryInfoContent is converted to functional component (#229)
* refactor:116[PackageList] component is converted to functional * Refactor:#116 - Registry info content is converted to functional component * refactor/116 - fix lint error * refactor:116 - more lint errors * refactor/116 - lint error * refactor:116 - remove snapshot * refactor: address code review comments #116 * refactor: fix lint error * refactor: code review changes * refactor add missed file * refactor: lint error * refactor: lint * refactor: lint * refactor: fix lint error
This commit is contained in:
parent
803da1c532
commit
b74ca2285e
@ -1,4 +1,4 @@
|
||||
import React, { Fragment, ReactElement } from 'react';
|
||||
import React, { Fragment, ReactNode } from 'react';
|
||||
|
||||
import Package from '../Package';
|
||||
import Help from '../Help';
|
||||
@ -12,22 +12,23 @@ interface Props {
|
||||
packages: PackageInterface[];
|
||||
}
|
||||
|
||||
export const PackageList: React.FC<Props> = props => {
|
||||
const renderPackages: () => ReactElement<HTMLElement>[] = () => {
|
||||
return props.packages.map((pkg, i) => {
|
||||
const { name, version, description, time, keywords, dist, homepage, bugs, author } = pkg;
|
||||
export const PackageList: React.FC<Props> = ({ packages }) => {
|
||||
const renderPackages: () => ReactNode[] = () => {
|
||||
return packages.map(({ name, version, description, time, keywords, dist, homepage, bugs, author, license }, i) => {
|
||||
// TODO: move format license to API side.
|
||||
const license = formatLicense(pkg.license);
|
||||
const _license = formatLicense(license);
|
||||
return (
|
||||
<Fragment key={i}>
|
||||
{i !== 0 && <Divider />}
|
||||
<Package {...{ name, dist, version, author, description, license, time, keywords, homepage, bugs }} />
|
||||
<Package
|
||||
{...{ name, dist, version, author, description, license: _license, time, keywords, homepage, bugs }}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const hasPackages: () => boolean = () => props.packages.length > 0;
|
||||
const hasPackages: () => boolean = () => packages.length > 0;
|
||||
|
||||
return (
|
||||
<div className={'package-list-items'}>
|
||||
|
@ -1,23 +1,35 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { render, cleanup, fireEvent } from '@testing-library/react';
|
||||
|
||||
import RegistryInfoContent from './RegistryInfoContent';
|
||||
|
||||
describe('<RegistryInfoContent /> component', () => {
|
||||
test('should render the component in default state with npm tab', () => {
|
||||
const wrapper = mount(<RegistryInfoContent registryUrl="https://registry.verdaccio.org" scope="@" />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test('should render the component in default state with pnpm tab', () => {
|
||||
const wrapper = mount(<RegistryInfoContent registryUrl="https://registry.verdaccio.org" scope="@" />);
|
||||
wrapper.setState({ tabPosition: 1 });
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
test('should load the component with no data', () => {
|
||||
const { getByTestId } = render(<RegistryInfoContent registryUrl={''} scope={''} />);
|
||||
const unorderedListOfTodos = getByTestId('tabs-el');
|
||||
expect(unorderedListOfTodos.children.length).toBe(1);
|
||||
});
|
||||
|
||||
test('should render the component in default state with yarn tab', () => {
|
||||
const wrapper = mount(<RegistryInfoContent registryUrl="https://registry.verdaccio.org" scope="@" />);
|
||||
wrapper.setState({ tabPosition: 2 });
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
test('should load the appropiate tab content when the tab is clicked', () => {
|
||||
const props = { registryUrl: 'http://localhost:4872', scope: '@' };
|
||||
const pnpmTabTextContent = `pnpm adduser --registry ${props.registryUrl}`;
|
||||
|
||||
// Render the component.
|
||||
const { container, getByTestId } = render(
|
||||
<RegistryInfoContent registryUrl={props.registryUrl} scope={props.scope} />
|
||||
);
|
||||
|
||||
// Assert the text content for pnpm tab is not present intially
|
||||
expect(container.textContent).not.toContain(pnpmTabTextContent);
|
||||
|
||||
const pnpmTab = getByTestId('pnpm-tab');
|
||||
fireEvent.click(pnpmTab);
|
||||
|
||||
// Assert the text content is correct after clicking on the tab.
|
||||
expect(container.textContent).toContain(pnpmTabTextContent);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
|
||||
import CopyToClipBoard from '../CopyToClipBoard';
|
||||
@ -11,8 +11,62 @@ import Tab from '../../muiComponents/Tab';
|
||||
import { CommandContainer } from './styles';
|
||||
import { Props, State } from './types';
|
||||
|
||||
/* eslint react/prop-types:0 */
|
||||
function TabContainer({ children }): JSX.Element {
|
||||
const RegistryInfoContent: React.FC<Props> = props => {
|
||||
const [tabPosition, setTabPosition] = useState<State['tabPosition']>(0);
|
||||
const handleChange = (event: React.ChangeEvent<{}>, tabPosition: number): void => {
|
||||
event.preventDefault();
|
||||
setTabPosition(tabPosition);
|
||||
};
|
||||
|
||||
const renderNpmTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLISetRegistry(`${NODE_MANAGER.npm} adduser`, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLIChangePassword(NODE_MANAGER.npm, registryUrl)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderPnpmTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLISetRegistry(`${NODE_MANAGER.pnpm} adduser`, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLIChangePassword(NODE_MANAGER.pnpm, registryUrl)} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderYarnTab = (scope: string, registryUrl: string): JSX.Element => {
|
||||
return <CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)} />;
|
||||
};
|
||||
|
||||
const renderTabs = (): JSX.Element => {
|
||||
const { scope, registryUrl } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
data-testid={'tabs-el'}
|
||||
indicatorColor="primary"
|
||||
onChange={handleChange}
|
||||
textColor="primary"
|
||||
value={tabPosition}
|
||||
variant="fullWidth">
|
||||
<Tab data-testid={'npm-tab'} label={NODE_MANAGER.npm} />
|
||||
<Tab data-testid={'pnpm-tab'} label={NODE_MANAGER.pnpm} />
|
||||
<Tab data-testid={'yarn-tab'} label={NODE_MANAGER.yarn} />
|
||||
</Tabs>
|
||||
{tabPosition === 0 && <TabContainer>{renderNpmTab(scope, registryUrl)}</TabContainer>}
|
||||
{tabPosition === 1 && <TabContainer>{renderPnpmTab(scope, registryUrl)}</TabContainer>}
|
||||
{tabPosition === 2 && <TabContainer>{renderYarnTab(scope, registryUrl)}</TabContainer>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/* eslint react/prop-types:0 */
|
||||
const TabContainer = ({ children }): JSX.Element => {
|
||||
return (
|
||||
<CommandContainer>
|
||||
<Typography
|
||||
@ -25,72 +79,9 @@ function TabContainer({ children }): JSX.Element {
|
||||
</Typography>
|
||||
</CommandContainer>
|
||||
);
|
||||
}
|
||||
|
||||
class RegistryInfoContent extends Component<Props, State> {
|
||||
public state = {
|
||||
tabPosition: 0,
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
return <div>{this.renderTabs()}</div>;
|
||||
}
|
||||
|
||||
private handleChange = (event: React.ChangeEvent<{}>, tabPosition: number) => {
|
||||
event.preventDefault();
|
||||
this.setState({ tabPosition });
|
||||
};
|
||||
|
||||
private renderTabs(): JSX.Element {
|
||||
const { scope, registryUrl } = this.props;
|
||||
const { tabPosition } = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Tabs
|
||||
indicatorColor="primary"
|
||||
onChange={this.handleChange}
|
||||
textColor="primary"
|
||||
value={tabPosition}
|
||||
variant="fullWidth">
|
||||
<Tab label={NODE_MANAGER.npm} />
|
||||
<Tab label={NODE_MANAGER.pnpm} />
|
||||
<Tab label={NODE_MANAGER.yarn} />
|
||||
</Tabs>
|
||||
{tabPosition === 0 && <TabContainer>{this.renderNpmTab(scope, registryUrl)}</TabContainer>}
|
||||
{tabPosition === 1 && <TabContainer>{this.renderPNpmTab(scope, registryUrl)}</TabContainer>}
|
||||
{tabPosition === 2 && <TabContainer>{this.renderYarnTab(scope, registryUrl)}</TabContainer>}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNpmTab(scope: string, registryUrl: string): JSX.Element {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLISetRegistry(`${NODE_MANAGER.npm} adduser`, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLIChangePassword(NODE_MANAGER.npm, registryUrl)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private renderPNpmTab(scope: string, registryUrl: string): JSX.Element {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLISetRegistry(`${NODE_MANAGER.pnpm} adduser`, registryUrl)} />
|
||||
<CopyToClipBoard text={getCLIChangePassword(NODE_MANAGER.pnpm, registryUrl)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private renderYarnTab(scope: string, registryUrl: string): JSX.Element {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<CopyToClipBoard text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
return <div>{renderTabs()}</div>;
|
||||
};
|
||||
|
||||
export default RegistryInfoContent;
|
||||
|
@ -1,7 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RegistryInfoContent /> component should render the component in default state with npm tab 1`] = `"<div><div class=\\"MuiTabs-root\\"><div class=\\"MuiTabs-scroller MuiTabs-fixed\\" style=\\"overflow: hidden;\\"><div class=\\"MuiTabs-flexContainer\\" role=\\"tablist\\"><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"true\\"><span class=\\"MuiTab-wrapper\\">npm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">pnpm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">yarn</span><span class=\\"MuiTouchRipple-root\\"></span></button></div><span class=\\"PrivateTabIndicator-root-25 PrivateTabIndicator-colorPrimary-26 MuiTabs-indicator\\" style=\\"left: 0px; width: 0px;\\"></span></div></div><div class=\\"css-1qvg11o ehfwshd0\\"><div class=\\"MuiTypography-root css-17iubtz MuiTypography-h6\\"><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm set @registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm adduser --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">npm profile set password --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div></div></div></div>"`;
|
||||
|
||||
exports[`<RegistryInfoContent /> component should render the component in default state with pnpm tab 1`] = `"<div><div class=\\"MuiTabs-root\\"><div class=\\"MuiTabs-scroller MuiTabs-fixed\\" style=\\"overflow: hidden;\\"><div class=\\"MuiTabs-flexContainer\\" role=\\"tablist\\"><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">npm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"true\\"><span class=\\"MuiTab-wrapper\\">pnpm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">yarn</span><span class=\\"MuiTouchRipple-root\\"></span></button></div><span class=\\"PrivateTabIndicator-root-25 PrivateTabIndicator-colorPrimary-26 MuiTabs-indicator\\" style=\\"left: 0px; width: 0px;\\"></span></div></div><div class=\\"css-1qvg11o ehfwshd0\\"><div class=\\"MuiTypography-root css-17iubtz MuiTypography-h6\\"><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">pnpm set @registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">pnpm adduser --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">pnpm profile set password --registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div></div></div></div>"`;
|
||||
|
||||
exports[`<RegistryInfoContent /> component should render the component in default state with yarn tab 1`] = `"<div><div class=\\"MuiTabs-root\\"><div class=\\"MuiTabs-scroller MuiTabs-fixed\\" style=\\"overflow: hidden;\\"><div class=\\"MuiTabs-flexContainer\\" role=\\"tablist\\"><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">npm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"false\\"><span class=\\"MuiTab-wrapper\\">pnpm</span><span class=\\"MuiTouchRipple-root\\"></span></button><button class=\\"MuiButtonBase-root MuiTab-root MuiTab-textColorPrimary Mui-selected MuiTab-fullWidth\\" tabindex=\\"0\\" type=\\"button\\" role=\\"tab\\" aria-selected=\\"true\\"><span class=\\"MuiTab-wrapper\\">yarn</span><span class=\\"MuiTouchRipple-root\\"></span></button></div><span class=\\"PrivateTabIndicator-root-25 PrivateTabIndicator-colorPrimary-26 MuiTabs-indicator\\" style=\\"left: 0px; width: 0px;\\"></span></div></div><div class=\\"css-1qvg11o ehfwshd0\\"><div class=\\"MuiTypography-root css-17iubtz MuiTypography-h6\\"><div class=\\"css-1mta3t8 eb8w2fo0\\"><span class=\\"css-lh0wgu eb8w2fo1\\">yarn config set @registry https://registry.verdaccio.org</span><button class=\\"MuiButtonBase-root MuiIconButton-root css-0 eb8w2fo2\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Copy to Clipboard\\"><span class=\\"MuiIconButton-label\\"><svg class=\\"MuiSvgIcon-root\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z\\"></path></svg></span><span class=\\"MuiTouchRipple-root\\"></span></button></div></div></div></div>"`;
|
Loading…
Reference in New Issue
Block a user