feat: implement multi token select component
This commit is contained in:
		
							
								
								
									
										31
									
								
								packages/website/ts/components/ui/check_mark.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/website/ts/components/ui/check_mark.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
 | 
			
		||||
import { colors } from '@0x/react-shared';
 | 
			
		||||
 | 
			
		||||
export interface CheckMarkProps {
 | 
			
		||||
    color?: string;
 | 
			
		||||
    isChecked?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CheckMark: React.StatelessComponent<CheckMarkProps> = ({ color, isChecked }) => (
 | 
			
		||||
    <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
        <circle
 | 
			
		||||
            cx="8.5"
 | 
			
		||||
            cy="8.5"
 | 
			
		||||
            r="8.5"
 | 
			
		||||
            fill={isChecked ? color : 'white'}
 | 
			
		||||
            stroke={isChecked ? undefined : '#CCCCCC'}
 | 
			
		||||
        />
 | 
			
		||||
        <path
 | 
			
		||||
            d="M2.5 4.5L1.79289 5.20711L2.5 5.91421L3.20711 5.20711L2.5 4.5ZM-0.707107 2.70711L1.79289 5.20711L3.20711 3.79289L0.707107 1.29289L-0.707107 2.70711ZM3.20711 5.20711L7.70711 0.707107L6.29289 -0.707107L1.79289 3.79289L3.20711 5.20711Z"
 | 
			
		||||
            transform="translate(5 6.5)"
 | 
			
		||||
            fill="white"
 | 
			
		||||
        />
 | 
			
		||||
    </svg>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
CheckMark.displayName = 'Check';
 | 
			
		||||
 | 
			
		||||
CheckMark.defaultProps = {
 | 
			
		||||
    color: colors.mediumBlue,
 | 
			
		||||
};
 | 
			
		||||
@@ -9,6 +9,7 @@ type StringOrNum = string | number;
 | 
			
		||||
export type ContainerTag = 'div' | 'span';
 | 
			
		||||
 | 
			
		||||
export interface ContainerProps {
 | 
			
		||||
    margin?: string;
 | 
			
		||||
    marginTop?: StringOrNum;
 | 
			
		||||
    marginBottom?: StringOrNum;
 | 
			
		||||
    marginRight?: StringOrNum;
 | 
			
		||||
@@ -48,7 +49,9 @@ export interface ContainerProps {
 | 
			
		||||
    id?: string;
 | 
			
		||||
    onClick?: (event: React.MouseEvent<HTMLElement>) => void;
 | 
			
		||||
    overflowX?: 'scroll' | 'hidden' | 'auto' | 'visible';
 | 
			
		||||
    overflowY?: 'scroll' | 'hidden' | 'auto' | 'visible';
 | 
			
		||||
    shouldDarkenOnHover?: boolean;
 | 
			
		||||
    hasBoxShadow?: boolean;
 | 
			
		||||
    shouldAddBoxShadowOnHover?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -62,6 +65,7 @@ export const PlainContainer: React.StatelessComponent<ContainerProps> = props =>
 | 
			
		||||
        onClick,
 | 
			
		||||
        shouldDarkenOnHover,
 | 
			
		||||
        shouldAddBoxShadowOnHover,
 | 
			
		||||
        hasBoxShadow,
 | 
			
		||||
        // tslint:disable-next-line:trailing-comma
 | 
			
		||||
        ...style
 | 
			
		||||
    } = props;
 | 
			
		||||
@@ -74,6 +78,8 @@ export const PlainContainer: React.StatelessComponent<ContainerProps> = props =>
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const Container = styled(PlainContainer)`
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
 | 
			
		||||
    &:hover {
 | 
			
		||||
        ${props =>
 | 
			
		||||
            props.shouldDarkenOnHover
 | 
			
		||||
 
 | 
			
		||||
@@ -2,40 +2,42 @@ import { colors } from '@0x/react-shared';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
 | 
			
		||||
import { zIndex } from 'ts/style/z_index';
 | 
			
		||||
 | 
			
		||||
import { Container } from './container';
 | 
			
		||||
import { Overlay } from './overlay';
 | 
			
		||||
import { Text } from './text';
 | 
			
		||||
 | 
			
		||||
export interface MultiSelectItemConfig {
 | 
			
		||||
    value: string;
 | 
			
		||||
    displayText: React.ReactNode;
 | 
			
		||||
    renderItemContent: (isSelected: boolean) => React.ReactNode;
 | 
			
		||||
    onClick?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MultiSelectProps {
 | 
			
		||||
    selectedValues: string[];
 | 
			
		||||
    selectedValues?: string[];
 | 
			
		||||
    items: MultiSelectItemConfig[];
 | 
			
		||||
    backgroundColor?: string;
 | 
			
		||||
    textColor?: string;
 | 
			
		||||
    height?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class MultiSelect extends React.Component<MultiSelectProps> {
 | 
			
		||||
    public static defaultProps = {
 | 
			
		||||
        backgroundColor: colors.white,
 | 
			
		||||
        textColor: colors.darkGrey,
 | 
			
		||||
    };
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
        const { items, backgroundColor, selectedValues } = this.props;
 | 
			
		||||
        const { items, backgroundColor, selectedValues, height } = this.props;
 | 
			
		||||
        return (
 | 
			
		||||
            <Container backgroundColor={backgroundColor} borderRadius="4px">
 | 
			
		||||
            <Container
 | 
			
		||||
                backgroundColor={backgroundColor}
 | 
			
		||||
                borderRadius="4px"
 | 
			
		||||
                width="100%"
 | 
			
		||||
                height={height}
 | 
			
		||||
                overflowY="scroll"
 | 
			
		||||
            >
 | 
			
		||||
                {_.map(items, item => (
 | 
			
		||||
                    <MultiSelectItem
 | 
			
		||||
                        key={item.value}
 | 
			
		||||
                        displayText={item.displayText}
 | 
			
		||||
                        renderItemContent={item.renderItemContent}
 | 
			
		||||
                        backgroundColor={backgroundColor}
 | 
			
		||||
                        onClick={item.onClick}
 | 
			
		||||
                        isSelected={_.includes(selectedValues, item.value)}
 | 
			
		||||
                        isSelected={_.isUndefined(selectedValues) || _.includes(selectedValues, item.value)}
 | 
			
		||||
                    />
 | 
			
		||||
                ))}
 | 
			
		||||
            </Container>
 | 
			
		||||
@@ -44,19 +46,21 @@ export class MultiSelect extends React.Component<MultiSelectProps> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface MultiSelectItemProps {
 | 
			
		||||
    displayText: React.ReactNode;
 | 
			
		||||
    renderItemContent: (isSelected: boolean) => React.ReactNode;
 | 
			
		||||
    isSelected?: boolean;
 | 
			
		||||
    onClick?: () => void;
 | 
			
		||||
    backgroundColor?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const MultiSelectItem: React.StatelessComponent<MultiSelectItemProps> = ({
 | 
			
		||||
    displayText,
 | 
			
		||||
    renderItemContent,
 | 
			
		||||
    isSelected,
 | 
			
		||||
    onClick,
 | 
			
		||||
    backgroundColor,
 | 
			
		||||
}) => (
 | 
			
		||||
    <Container shouldDarkenOnHover={true} onClick={onClick}>
 | 
			
		||||
        <Container borderBottom="1px solid black" padding="0px 15px">
 | 
			
		||||
            {displayText}
 | 
			
		||||
    <Container cursor="pointer" shouldDarkenOnHover={true} onClick={onClick} backgroundColor={backgroundColor}>
 | 
			
		||||
        <Container borderBottom={`1px solid ${colors.lightestGrey}`} margin="0px 15px" padding="10px 0px">
 | 
			
		||||
            {renderItemContent(isSelected)}
 | 
			
		||||
        </Container>
 | 
			
		||||
    </Container>
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
 | 
			
		||||
                        cursor={hasItems ? 'pointer' : undefined}
 | 
			
		||||
                        onClick={this._handleDropdownClick}
 | 
			
		||||
                        borderRadius={borderRadius}
 | 
			
		||||
                        hasBoxShadow={isOpen}
 | 
			
		||||
                        border={border}
 | 
			
		||||
                        backgroundColor={backgroundColor}
 | 
			
		||||
                        padding="0.8em"
 | 
			
		||||
@@ -94,6 +95,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
 | 
			
		||||
                            position="absolute"
 | 
			
		||||
                            onClick={this._closeDropdown}
 | 
			
		||||
                            zIndex={zIndex.aboveOverlay}
 | 
			
		||||
                            hasBoxShadow={true}
 | 
			
		||||
                        >
 | 
			
		||||
                            {_.map(items, (item, index) => (
 | 
			
		||||
                                <SelectItem
 | 
			
		||||
 
 | 
			
		||||
@@ -23,12 +23,7 @@ export class ActionLink extends React.Component<ActionLinkProps> {
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
        const { displayText, fontSize, color, className } = this.props;
 | 
			
		||||
        return (
 | 
			
		||||
            <Container
 | 
			
		||||
                className={`flex items-center ${className}`}
 | 
			
		||||
                marginRight="32px"
 | 
			
		||||
                onClick={this._handleClick}
 | 
			
		||||
                cursor="pointer"
 | 
			
		||||
            >
 | 
			
		||||
            <Container className={`flex items-center ${className}`} onClick={this._handleClick} cursor="pointer">
 | 
			
		||||
                <Container>
 | 
			
		||||
                    <Text fontSize="16px" fontColor={color}>
 | 
			
		||||
                        {displayText}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import { ObjectMap } from '@0x/types';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
 | 
			
		||||
import { CheckMark } from 'ts/components/ui/check_mark';
 | 
			
		||||
import { Container } from 'ts/components/ui/container';
 | 
			
		||||
import { MultiSelect } from 'ts/components/ui/multi_select';
 | 
			
		||||
import { Select, SelectItemConfig } from 'ts/components/ui/select';
 | 
			
		||||
@@ -54,42 +55,79 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {
 | 
			
		||||
                <ConfigGeneratorSection title="Standard Relayer API Endpoint">
 | 
			
		||||
                    <Select value={value.orderSource} items={this._generateItems()} />
 | 
			
		||||
                </ConfigGeneratorSection>
 | 
			
		||||
                <ConfigGeneratorSection title="What tokens can users buy?">
 | 
			
		||||
                <ConfigGeneratorSection {...this._getTokenSelectorProps()}>
 | 
			
		||||
                    {this._renderTokenMultiSelectOrSpinner()}
 | 
			
		||||
                </ConfigGeneratorSection>
 | 
			
		||||
            </Container>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    private readonly _getTokenSelectorProps = (): ConfigGeneratorSectionProps => {
 | 
			
		||||
        if (_.isUndefined(this.props.value.availableAssetDatas)) {
 | 
			
		||||
            return {
 | 
			
		||||
                title: 'What tokens can users buy?',
 | 
			
		||||
                actionText: 'Unselect All',
 | 
			
		||||
                onActionTextClick: this._handleUnselectAllClick,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
            title: 'What tokens can users buy?',
 | 
			
		||||
            actionText: 'Select All',
 | 
			
		||||
            onActionTextClick: this._handleSelectAllClick,
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
    private readonly _generateItems = (): SelectItemConfig[] => {
 | 
			
		||||
        return _.map(SRA_ENDPOINTS, endpoint => ({
 | 
			
		||||
            text: endpoint,
 | 
			
		||||
            onClick: this._handleSRASelection.bind(this, endpoint),
 | 
			
		||||
        }));
 | 
			
		||||
    };
 | 
			
		||||
    private readonly _getAllKnownAssetDatas = (): string[] => {
 | 
			
		||||
        return _.map(this.state.allKnownTokens, token => assetDataUtils.encodeERC20AssetData(token.address));
 | 
			
		||||
    };
 | 
			
		||||
    private readonly _handleSRASelection = (sraEndpoint: string) => {
 | 
			
		||||
        const newConfig = {
 | 
			
		||||
        const newConfig: ZeroExInstantBaseConfig = {
 | 
			
		||||
            ...this.props.value,
 | 
			
		||||
            orderSource: sraEndpoint,
 | 
			
		||||
        };
 | 
			
		||||
        this.props.onConfigChange(newConfig);
 | 
			
		||||
    };
 | 
			
		||||
    private readonly _handleSelectAllClick = () => {
 | 
			
		||||
        const newConfig: ZeroExInstantBaseConfig = {
 | 
			
		||||
            ...this.props.value,
 | 
			
		||||
            availableAssetDatas: undefined,
 | 
			
		||||
        };
 | 
			
		||||
        this.props.onConfigChange(newConfig);
 | 
			
		||||
    };
 | 
			
		||||
    private readonly _handleUnselectAllClick = () => {
 | 
			
		||||
        const newConfig: ZeroExInstantBaseConfig = {
 | 
			
		||||
            ...this.props.value,
 | 
			
		||||
            availableAssetDatas: [],
 | 
			
		||||
        };
 | 
			
		||||
        this.props.onConfigChange(newConfig);
 | 
			
		||||
    };
 | 
			
		||||
    private readonly _handleTokenClick = (assetData: string) => {
 | 
			
		||||
        const { value } = this.props;
 | 
			
		||||
        let newAvailableAssetDatas = [];
 | 
			
		||||
        if (_.includes(value.availableAssetDatas, assetData)) {
 | 
			
		||||
        const { allKnownTokens } = this.state;
 | 
			
		||||
        let newAvailableAssetDatas: string[] = [];
 | 
			
		||||
        const availableAssetDatas = value.availableAssetDatas;
 | 
			
		||||
        if (_.isUndefined(availableAssetDatas)) {
 | 
			
		||||
            // It being undefined means it's all tokens.
 | 
			
		||||
            const allKnownAssetDatas = this._getAllKnownAssetDatas();
 | 
			
		||||
            newAvailableAssetDatas = _.pull(allKnownAssetDatas, assetData);
 | 
			
		||||
        } else if (!_.includes(availableAssetDatas, assetData)) {
 | 
			
		||||
            // Add it
 | 
			
		||||
            newAvailableAssetDatas = [...value.availableAssetDatas, assetData];
 | 
			
		||||
            newAvailableAssetDatas = [...availableAssetDatas, assetData];
 | 
			
		||||
        } else {
 | 
			
		||||
            // Remove it
 | 
			
		||||
            newAvailableAssetDatas = _.remove(value.availableAssetDatas, assetData);
 | 
			
		||||
            newAvailableAssetDatas = _.pull(availableAssetDatas, assetData);
 | 
			
		||||
        }
 | 
			
		||||
        const newConfig = {
 | 
			
		||||
        const newConfig: ZeroExInstantBaseConfig = {
 | 
			
		||||
            ...this.props.value,
 | 
			
		||||
            availableAssetDatas: newAvailableAssetDatas,
 | 
			
		||||
        };
 | 
			
		||||
        this.props.onConfigChange(newConfig);
 | 
			
		||||
    };
 | 
			
		||||
    private _setAllKnownTokens = async (callback: () => void): Promise<void> => {
 | 
			
		||||
    private readonly _setAllKnownTokens = async (callback: () => void): Promise<void> => {
 | 
			
		||||
        const tokenInfos = await backendClient.getTokenInfosAsync();
 | 
			
		||||
        const allKnownTokens = _.reduce(
 | 
			
		||||
            tokenInfos,
 | 
			
		||||
@@ -101,7 +139,7 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {
 | 
			
		||||
        );
 | 
			
		||||
        this.setState({ allKnownTokens }, callback);
 | 
			
		||||
    };
 | 
			
		||||
    private _setAvailableAssetsFromOrderProvider = async (): Promise<void> => {
 | 
			
		||||
    private readonly _setAvailableAssetsFromOrderProvider = async (): Promise<void> => {
 | 
			
		||||
        const { value } = this.props;
 | 
			
		||||
        if (!_.isUndefined(value.orderSource) && _.isString(value.orderSource)) {
 | 
			
		||||
            this.setState({ isLoadingAvailableTokens: true });
 | 
			
		||||
@@ -119,13 +157,23 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {
 | 
			
		||||
            this.setState({ availableTokens, isLoadingAvailableTokens: false });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    private _renderTokenMultiSelectOrSpinner = (): React.ReactNode => {
 | 
			
		||||
    private readonly _renderTokenMultiSelectOrSpinner = (): React.ReactNode => {
 | 
			
		||||
        const { value } = this.props;
 | 
			
		||||
        const { availableTokens, isLoadingAvailableTokens } = this.state;
 | 
			
		||||
        const multiSelectHeight = '200px';
 | 
			
		||||
        if (isLoadingAvailableTokens) {
 | 
			
		||||
            return (
 | 
			
		||||
                <Container className="flex items-center">
 | 
			
		||||
                    <Spinner />
 | 
			
		||||
                <Container
 | 
			
		||||
                    className="flex flex-column items-center justify-center"
 | 
			
		||||
                    height={multiSelectHeight}
 | 
			
		||||
                    backgroundColor={colors.white}
 | 
			
		||||
                    borderRadius="4px"
 | 
			
		||||
                    width="100%"
 | 
			
		||||
                >
 | 
			
		||||
                    <Container position="relative" left="12px" marginBottom="20px">
 | 
			
		||||
                        <Spinner />
 | 
			
		||||
                    </Container>
 | 
			
		||||
                    <Text fontSize="16px">Loading...</Text>
 | 
			
		||||
                </Container>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
@@ -133,15 +181,24 @@ export class ConfigGenerator extends React.Component<ConfigGeneratorProps> {
 | 
			
		||||
            const assetData = assetDataUtils.encodeERC20AssetData(token.address);
 | 
			
		||||
            return {
 | 
			
		||||
                value: assetDataUtils.encodeERC20AssetData(token.address),
 | 
			
		||||
                displayText: (
 | 
			
		||||
                    <Text>
 | 
			
		||||
                        <b>{token.symbol}</b> - {token.name}
 | 
			
		||||
                    </Text>
 | 
			
		||||
                renderItemContent: (isSelected: boolean) => (
 | 
			
		||||
                    <Container className="flex items-center">
 | 
			
		||||
                        <Container marginRight="10px">
 | 
			
		||||
                            <CheckMark isChecked={isSelected} />
 | 
			
		||||
                        </Container>
 | 
			
		||||
                        <Text
 | 
			
		||||
                            fontSize="16px"
 | 
			
		||||
                            fontColor={isSelected ? colors.mediumBlue : colors.darkerGrey}
 | 
			
		||||
                            fontWeight={300}
 | 
			
		||||
                        >
 | 
			
		||||
                            <b>{token.symbol}</b> — {token.name}
 | 
			
		||||
                        </Text>
 | 
			
		||||
                    </Container>
 | 
			
		||||
                ),
 | 
			
		||||
                onClick: this._handleTokenClick.bind(this, assetData),
 | 
			
		||||
            };
 | 
			
		||||
        });
 | 
			
		||||
        return <MultiSelect items={items} selectedValues={value.availableAssetDatas || []} />;
 | 
			
		||||
        return <MultiSelect items={items} selectedValues={value.availableAssetDatas} height={multiSelectHeight} />;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -151,13 +208,23 @@ export interface ConfigGeneratorSectionProps {
 | 
			
		||||
    onActionTextClick?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = props => (
 | 
			
		||||
export const ConfigGeneratorSection: React.StatelessComponent<ConfigGeneratorSectionProps> = ({
 | 
			
		||||
    title,
 | 
			
		||||
    actionText,
 | 
			
		||||
    onActionTextClick,
 | 
			
		||||
    children,
 | 
			
		||||
}) => (
 | 
			
		||||
    <Container marginBottom="30px">
 | 
			
		||||
        <Container marginBottom="10px">
 | 
			
		||||
        <Container marginBottom="10px" className="flex justify-between items-center">
 | 
			
		||||
            <Text fontColor={colors.white} fontSize="16px" lineHeight="18px">
 | 
			
		||||
                {props.title}
 | 
			
		||||
                {title}
 | 
			
		||||
            </Text>
 | 
			
		||||
            {actionText && (
 | 
			
		||||
                <Text fontSize="12px" fontColor={colors.grey} onClick={onActionTextClick}>
 | 
			
		||||
                    {actionText}
 | 
			
		||||
                </Text>
 | 
			
		||||
            )}
 | 
			
		||||
        </Container>
 | 
			
		||||
        {props.children}
 | 
			
		||||
        {children}
 | 
			
		||||
    </Container>
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,10 @@ export interface ConfiguratorState {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Configurator extends React.Component<ConfiguratorProps> {
 | 
			
		||||
    public state = {
 | 
			
		||||
    public state: ConfiguratorState = {
 | 
			
		||||
        instantConfig: {
 | 
			
		||||
            orderSource: 'https://api.radarrelay.com/0x/v2/',
 | 
			
		||||
            availableAssetDatas: [],
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
    public render(): React.ReactNode {
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,11 @@ const FeatureItem = (props: FeatureItemProps) => {
 | 
			
		||||
                </Text>
 | 
			
		||||
            </Container>
 | 
			
		||||
            <Container className="flex" marginTop="28px">
 | 
			
		||||
                {_.map(linkInfos, linkInfo => <ActionLink key={linkInfo.displayText} {...linkInfo} />)}
 | 
			
		||||
                {_.map(linkInfos, linkInfo => (
 | 
			
		||||
                    <Container marginRight="32px">
 | 
			
		||||
                        <ActionLink key={linkInfo.displayText} {...linkInfo} />
 | 
			
		||||
                    </Container>
 | 
			
		||||
                ))}
 | 
			
		||||
            </Container>
 | 
			
		||||
        </Container>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user