mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Visual updates to constraints (#1157)
This commit is contained in:
		
							parent
							
								
									7df59cf286
								
							
						
					
					
						commit
						c20aa300ce
					
				@ -52,10 +52,13 @@ export const useStyles = makeStyles()(theme => ({
 | 
				
			|||||||
    headerValuesContainerWrapper: {
 | 
					    headerValuesContainerWrapper: {
 | 
				
			||||||
        display: 'flex',
 | 
					        display: 'flex',
 | 
				
			||||||
        alignItems: 'stretch',
 | 
					        alignItems: 'stretch',
 | 
				
			||||||
 | 
					        margin: 'auto 0',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    headerValuesContainer: {
 | 
					    headerValuesContainer: {
 | 
				
			||||||
        display: 'flex',
 | 
					        display: 'flex',
 | 
				
			||||||
        alignItems: 'stretch',
 | 
					        justifyContent: 'stretch',
 | 
				
			||||||
 | 
					        margin: 'auto 0',
 | 
				
			||||||
 | 
					        flexDirection: 'column',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    headerValues: {
 | 
					    headerValues: {
 | 
				
			||||||
        fontSize: theme.fontSizes.smallBody,
 | 
					        fontSize: theme.fontSizes.smallBody,
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@ export const useStyles = makeStyles()(theme => ({
 | 
				
			|||||||
        margin: '0.75rem 0 ',
 | 
					        margin: '0.75rem 0 ',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    customConstraintLabel: {
 | 
					    customConstraintLabel: {
 | 
				
			||||||
 | 
					        marginBottom: theme.spacing(1),
 | 
				
			||||||
        color: theme.palette.text.secondary,
 | 
					        color: theme.palette.text.secondary,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import React, { forwardRef, useImperativeHandle } from 'react';
 | 
					import React, { forwardRef, Fragment, useImperativeHandle } from 'react';
 | 
				
			||||||
import { Button, Tooltip } from '@mui/material';
 | 
					import { Button, Tooltip } from '@mui/material';
 | 
				
			||||||
import { HelpOutline } from '@mui/icons-material';
 | 
					import { HelpOutline } from '@mui/icons-material';
 | 
				
			||||||
import { IConstraint } from 'interfaces/strategy';
 | 
					import { IConstraint } from 'interfaces/strategy';
 | 
				
			||||||
@ -10,11 +10,14 @@ import { objectId } from 'utils/objectId';
 | 
				
			|||||||
import { useStyles } from './ConstraintAccordionList.styles';
 | 
					import { useStyles } from './ConstraintAccordionList.styles';
 | 
				
			||||||
import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
 | 
					import { createEmptyConstraint } from 'component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint';
 | 
				
			||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
					import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
				
			||||||
 | 
					import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IConstraintAccordionListProps {
 | 
					interface IConstraintAccordionListProps {
 | 
				
			||||||
    constraints: IConstraint[];
 | 
					    constraints: IConstraint[];
 | 
				
			||||||
    setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>;
 | 
					    setConstraints?: React.Dispatch<React.SetStateAction<IConstraint[]>>;
 | 
				
			||||||
    showCreateButton?: boolean;
 | 
					    showCreateButton?: boolean;
 | 
				
			||||||
 | 
					    /* Add "Custom constraints" title on the top - default `true` */
 | 
				
			||||||
 | 
					    showLabel?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Ref methods exposed by this component.
 | 
					// Ref methods exposed by this component.
 | 
				
			||||||
@ -35,124 +38,138 @@ export const constraintAccordionListId = 'constraintAccordionListId';
 | 
				
			|||||||
export const ConstraintAccordionList = forwardRef<
 | 
					export const ConstraintAccordionList = forwardRef<
 | 
				
			||||||
    IConstraintAccordionListRef | undefined,
 | 
					    IConstraintAccordionListRef | undefined,
 | 
				
			||||||
    IConstraintAccordionListProps
 | 
					    IConstraintAccordionListProps
 | 
				
			||||||
>(({ constraints, setConstraints, showCreateButton }, ref) => {
 | 
					>(
 | 
				
			||||||
    const state = useWeakMap<IConstraint, IConstraintAccordionListItemState>();
 | 
					    (
 | 
				
			||||||
    const { context } = useUnleashContext();
 | 
					        { constraints, setConstraints, showCreateButton, showLabel = true },
 | 
				
			||||||
    const { classes: styles } = useStyles();
 | 
					        ref
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					        const state = useWeakMap<
 | 
				
			||||||
 | 
					            IConstraint,
 | 
				
			||||||
 | 
					            IConstraintAccordionListItemState
 | 
				
			||||||
 | 
					        >();
 | 
				
			||||||
 | 
					        const { context } = useUnleashContext();
 | 
				
			||||||
 | 
					        const { classes: styles } = useStyles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const addConstraint =
 | 
					        const addConstraint =
 | 
				
			||||||
        setConstraints &&
 | 
					            setConstraints &&
 | 
				
			||||||
        ((contextName: string) => {
 | 
					            ((contextName: string) => {
 | 
				
			||||||
            const constraint = createEmptyConstraint(contextName);
 | 
					                const constraint = createEmptyConstraint(contextName);
 | 
				
			||||||
            state.set(constraint, { editing: true, new: true });
 | 
					                state.set(constraint, { editing: true, new: true });
 | 
				
			||||||
            setConstraints(prev => [...prev, constraint]);
 | 
					                setConstraints(prev => [...prev, constraint]);
 | 
				
			||||||
        });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useImperativeHandle(ref, () => ({
 | 
					        useImperativeHandle(ref, () => ({
 | 
				
			||||||
        addConstraint,
 | 
					            addConstraint,
 | 
				
			||||||
    }));
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onAdd =
 | 
					        const onAdd =
 | 
				
			||||||
        addConstraint &&
 | 
					            addConstraint &&
 | 
				
			||||||
        (() => {
 | 
					            (() => {
 | 
				
			||||||
            addConstraint(context[0].name);
 | 
					                addConstraint(context[0].name);
 | 
				
			||||||
        });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onEdit =
 | 
					        const onEdit =
 | 
				
			||||||
        setConstraints &&
 | 
					            setConstraints &&
 | 
				
			||||||
        ((constraint: IConstraint) => {
 | 
					            ((constraint: IConstraint) => {
 | 
				
			||||||
            state.set(constraint, { editing: true });
 | 
					                state.set(constraint, { editing: true });
 | 
				
			||||||
        });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onRemove =
 | 
					        const onRemove =
 | 
				
			||||||
        setConstraints &&
 | 
					            setConstraints &&
 | 
				
			||||||
        ((index: number) => {
 | 
					            ((index: number) => {
 | 
				
			||||||
 | 
					                const constraint = constraints[index];
 | 
				
			||||||
 | 
					                state.set(constraint, {});
 | 
				
			||||||
 | 
					                setConstraints(
 | 
				
			||||||
 | 
					                    produce(draft => {
 | 
				
			||||||
 | 
					                        draft.splice(index, 1);
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const onSave =
 | 
				
			||||||
 | 
					            setConstraints &&
 | 
				
			||||||
 | 
					            ((index: number, constraint: IConstraint) => {
 | 
				
			||||||
 | 
					                state.set(constraint, {});
 | 
				
			||||||
 | 
					                setConstraints(
 | 
				
			||||||
 | 
					                    produce(draft => {
 | 
				
			||||||
 | 
					                        draft[index] = constraint;
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const onCancel = (index: number) => {
 | 
				
			||||||
            const constraint = constraints[index];
 | 
					            const constraint = constraints[index];
 | 
				
			||||||
 | 
					            state.get(constraint)?.new && onRemove?.(index);
 | 
				
			||||||
            state.set(constraint, {});
 | 
					            state.set(constraint, {});
 | 
				
			||||||
            setConstraints(
 | 
					        };
 | 
				
			||||||
                produce(draft => {
 | 
					 | 
				
			||||||
                    draft.splice(index, 1);
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onSave =
 | 
					        if (context.length === 0) {
 | 
				
			||||||
        setConstraints &&
 | 
					            return null;
 | 
				
			||||||
        ((index: number, constraint: IConstraint) => {
 | 
					        }
 | 
				
			||||||
            state.set(constraint, {});
 | 
					 | 
				
			||||||
            setConstraints(
 | 
					 | 
				
			||||||
                produce(draft => {
 | 
					 | 
				
			||||||
                    draft[index] = constraint;
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onCancel = (index: number) => {
 | 
					        return (
 | 
				
			||||||
        const constraint = constraints[index];
 | 
					            <div className={styles.container} id={constraintAccordionListId}>
 | 
				
			||||||
        state.get(constraint)?.new && onRemove?.(index);
 | 
					                <ConditionallyRender
 | 
				
			||||||
        state.set(constraint, {});
 | 
					                    condition={
 | 
				
			||||||
    };
 | 
					                        constraints && constraints.length > 0 && showLabel
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
    if (context.length === 0) {
 | 
					                    show={
 | 
				
			||||||
        return null;
 | 
					                        <p className={styles.customConstraintLabel}>
 | 
				
			||||||
    }
 | 
					                            Custom constraints
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
    return (
 | 
					                    }
 | 
				
			||||||
        <div className={styles.container} id={constraintAccordionListId}>
 | 
					 | 
				
			||||||
            <ConditionallyRender
 | 
					 | 
				
			||||||
                condition={constraints && constraints.length > 0}
 | 
					 | 
				
			||||||
                show={
 | 
					 | 
				
			||||||
                    <p className={styles.customConstraintLabel}>
 | 
					 | 
				
			||||||
                        Custom constraints
 | 
					 | 
				
			||||||
                    </p>
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            {constraints.map((constraint, index) => (
 | 
					 | 
				
			||||||
                <ConstraintAccordion
 | 
					 | 
				
			||||||
                    key={objectId(constraint)}
 | 
					 | 
				
			||||||
                    constraint={constraint}
 | 
					 | 
				
			||||||
                    onEdit={onEdit && onEdit.bind(null, constraint)}
 | 
					 | 
				
			||||||
                    onCancel={onCancel.bind(null, index)}
 | 
					 | 
				
			||||||
                    onDelete={onRemove && onRemove.bind(null, index)}
 | 
					 | 
				
			||||||
                    onSave={onSave && onSave.bind(null, index)}
 | 
					 | 
				
			||||||
                    editing={Boolean(state.get(constraint)?.editing)}
 | 
					 | 
				
			||||||
                    compact
 | 
					 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            ))}
 | 
					                {constraints.map((constraint, index) => (
 | 
				
			||||||
            <ConditionallyRender
 | 
					                    <Fragment key={objectId(constraint)}>
 | 
				
			||||||
                condition={Boolean(showCreateButton && onAdd)}
 | 
					                        <ConditionallyRender
 | 
				
			||||||
                show={
 | 
					                            condition={index > 0}
 | 
				
			||||||
                    <div>
 | 
					                            show={<StrategySeparator text="AND" />}
 | 
				
			||||||
                        <div className={styles.addCustomLabel}>
 | 
					                        />
 | 
				
			||||||
                            <p>Add any number of custom constraints</p>
 | 
					                        <ConstraintAccordion
 | 
				
			||||||
                            <Tooltip
 | 
					                            constraint={constraint}
 | 
				
			||||||
                                title="Help"
 | 
					                            onEdit={onEdit && onEdit.bind(null, constraint)}
 | 
				
			||||||
                                arrow
 | 
					                            onCancel={onCancel.bind(null, index)}
 | 
				
			||||||
                                className={styles.helpWrapper}
 | 
					                            onDelete={onRemove && onRemove.bind(null, index)}
 | 
				
			||||||
                            >
 | 
					                            onSave={onSave && onSave.bind(null, index)}
 | 
				
			||||||
                                <a
 | 
					                            editing={Boolean(state.get(constraint)?.editing)}
 | 
				
			||||||
                                    href={
 | 
					                            compact
 | 
				
			||||||
                                        'https://docs.getunleash.io/advanced/strategy_constraints'
 | 
					                        />
 | 
				
			||||||
                                    }
 | 
					                    </Fragment>
 | 
				
			||||||
                                    target="_blank"
 | 
					                ))}
 | 
				
			||||||
                                    rel="noopener noreferrer"
 | 
					                <ConditionallyRender
 | 
				
			||||||
 | 
					                    condition={Boolean(showCreateButton && onAdd)}
 | 
				
			||||||
 | 
					                    show={
 | 
				
			||||||
 | 
					                        <div>
 | 
				
			||||||
 | 
					                            <div className={styles.addCustomLabel}>
 | 
				
			||||||
 | 
					                                <p>Add any number of custom constraints</p>
 | 
				
			||||||
 | 
					                                <Tooltip
 | 
				
			||||||
 | 
					                                    title="Help"
 | 
				
			||||||
 | 
					                                    arrow
 | 
				
			||||||
 | 
					                                    className={styles.helpWrapper}
 | 
				
			||||||
                                >
 | 
					                                >
 | 
				
			||||||
                                    <HelpOutline className={styles.help} />
 | 
					                                    <a
 | 
				
			||||||
                                </a>
 | 
					                                        href={
 | 
				
			||||||
                            </Tooltip>
 | 
					                                            'https://docs.getunleash.io/advanced/strategy_constraints'
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                        target="_blank"
 | 
				
			||||||
 | 
					                                        rel="noopener noreferrer"
 | 
				
			||||||
 | 
					                                    >
 | 
				
			||||||
 | 
					                                        <HelpOutline className={styles.help} />
 | 
				
			||||||
 | 
					                                    </a>
 | 
				
			||||||
 | 
					                                </Tooltip>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <Button
 | 
				
			||||||
 | 
					                                type="button"
 | 
				
			||||||
 | 
					                                onClick={onAdd}
 | 
				
			||||||
 | 
					                                variant="outlined"
 | 
				
			||||||
 | 
					                                color="secondary"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                Add custom constraint
 | 
				
			||||||
 | 
					                            </Button>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <Button
 | 
					                    }
 | 
				
			||||||
                            type="button"
 | 
					                />
 | 
				
			||||||
                            onClick={onAdd}
 | 
					            </div>
 | 
				
			||||||
                            variant="outlined"
 | 
					        );
 | 
				
			||||||
                            color="secondary"
 | 
					    }
 | 
				
			||||||
                            sx={{ mb: 2 }}
 | 
					);
 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                            Add custom constraint
 | 
					 | 
				
			||||||
                        </Button>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ interface IStrategySeparatorProps {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
					const StyledContainer = styled('div')(({ theme }) => ({
 | 
				
			||||||
    height: theme.spacing(2),
 | 
					    height: theme.spacing(1),
 | 
				
			||||||
    position: 'relative',
 | 
					    position: 'relative',
 | 
				
			||||||
    width: '100%',
 | 
					    width: '100%',
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
				
			|||||||
@ -256,7 +256,10 @@ export const StrategyExecution = ({ strategy }: IStrategyExecutionProps) => {
 | 
				
			|||||||
                condition={constraints.length > 0}
 | 
					                condition={constraints.length > 0}
 | 
				
			||||||
                show={
 | 
					                show={
 | 
				
			||||||
                    <>
 | 
					                    <>
 | 
				
			||||||
                        <ConstraintAccordionList constraints={constraints} />
 | 
					                        <ConstraintAccordionList
 | 
				
			||||||
 | 
					                            constraints={constraints}
 | 
				
			||||||
 | 
					                            showLabel={false}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
                        <StrategySeparator text="AND" />
 | 
					                        <StrategySeparator text="AND" />
 | 
				
			||||||
                    </>
 | 
					                    </>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ export const useStyles = makeStyles()(theme => ({
 | 
				
			|||||||
        alignItems: 'center',
 | 
					        alignItems: 'center',
 | 
				
			||||||
        borderBottom: `1px solid ${theme.palette.grey[300]}`,
 | 
					        borderBottom: `1px solid ${theme.palette.grey[300]}`,
 | 
				
			||||||
        fontWeight: theme.typography.fontWeightMedium,
 | 
					        fontWeight: theme.typography.fontWeightMedium,
 | 
				
			||||||
 | 
					        fontSize: theme.fontSizes.smallBody,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    icon: {
 | 
					    icon: {
 | 
				
			||||||
        fill: theme.palette.inactiveIcon,
 | 
					        fill: theme.palette.inactiveIcon,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,16 +3,18 @@ import { makeStyles } from 'tss-react/mui';
 | 
				
			|||||||
export const useStyles = makeStyles()(theme => ({
 | 
					export const useStyles = makeStyles()(theme => ({
 | 
				
			||||||
    container: {
 | 
					    container: {
 | 
				
			||||||
        width: '100%',
 | 
					        width: '100%',
 | 
				
			||||||
        padding: '1rem',
 | 
					        padding: theme.spacing(2, 3),
 | 
				
			||||||
 | 
					        display: 'flex',
 | 
				
			||||||
 | 
					        alignItems: 'center',
 | 
				
			||||||
 | 
					        justifyContent: 'flex-start',
 | 
				
			||||||
        fontSize: theme.fontSizes.smallBody,
 | 
					        fontSize: theme.fontSizes.smallBody,
 | 
				
			||||||
        backgroundColor: theme.palette.grey[200],
 | 
					        border: `1px solid ${theme.palette.dividerAlternative}`,
 | 
				
			||||||
        position: 'relative',
 | 
					        position: 'relative',
 | 
				
			||||||
        borderRadius: '5px',
 | 
					        borderRadius: '5px',
 | 
				
			||||||
        textAlign: 'center',
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    link: {
 | 
					    link: {
 | 
				
			||||||
        textDecoration: 'none',
 | 
					        textDecoration: 'none',
 | 
				
			||||||
        fontWeight: theme.fontWeight.bold,
 | 
					        marginLeft: theme.spacing(1),
 | 
				
			||||||
        '&:hover': {
 | 
					        '&:hover': {
 | 
				
			||||||
            textDecoration: 'underline',
 | 
					            textDecoration: 'underline',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
 | 
					import { Fragment } from 'react';
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					import { DonutLarge } from '@mui/icons-material';
 | 
				
			||||||
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
 | 
					import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment.styles';
 | 
				
			||||||
import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
 | 
					import { useSegments } from 'hooks/api/getters/useSegments/useSegments';
 | 
				
			||||||
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
 | 
					import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
 | 
				
			||||||
import { Link } from 'react-router-dom';
 | 
					 | 
				
			||||||
import { Fragment } from 'react';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IFeatureOverviewSegmentProps {
 | 
					interface IFeatureOverviewSegmentProps {
 | 
				
			||||||
    strategyId: string;
 | 
					    strategyId: string;
 | 
				
			||||||
@ -23,7 +24,7 @@ export const FeatureOverviewSegment = ({
 | 
				
			|||||||
            {segments.map(segment => (
 | 
					            {segments.map(segment => (
 | 
				
			||||||
                <Fragment key={segment.id}>
 | 
					                <Fragment key={segment.id}>
 | 
				
			||||||
                    <div className={styles.container}>
 | 
					                    <div className={styles.container}>
 | 
				
			||||||
                        Segment{' '}
 | 
					                        <DonutLarge color="secondary" sx={{ mr: 1 }} /> Segment:{' '}
 | 
				
			||||||
                        <Link
 | 
					                        <Link
 | 
				
			||||||
                            to={segmentPath(segment.id)}
 | 
					                            to={segmentPath(segment.id)}
 | 
				
			||||||
                            className={styles.link}
 | 
					                            className={styles.link}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user