✨ New nav without responsive
This commit is contained in:
parent
4960b51ee3
commit
8ff373271b
@ -1,48 +0,0 @@
|
|||||||
import React, { useCallback } from "react";
|
|
||||||
import classes from "./classes.module.scss";
|
|
||||||
import Typography, { ETypo } from "../Typography";
|
|
||||||
import ChevronIcon from "@Assets/Icons/chevron.svg";
|
|
||||||
import Image from "next/image";
|
|
||||||
import WarningBadge from "../WarningBadge";
|
|
||||||
|
|
||||||
export type IBlock = {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
selected: boolean;
|
|
||||||
rightIcon?: JSX.Element;
|
|
||||||
hasFlag?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IProps = {
|
|
||||||
blocks: IBlock[];
|
|
||||||
onSelectedBlock: (block: IBlock) => void;
|
|
||||||
};
|
|
||||||
export default function BlockList({ blocks, onSelectedBlock }: IProps) {
|
|
||||||
const selectBlock = useCallback(
|
|
||||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
||||||
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === e.currentTarget.id)!);
|
|
||||||
},
|
|
||||||
[blocks, onSelectedBlock],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{blocks.map((folder) => {
|
|
||||||
return (
|
|
||||||
<div onClick={selectBlock} key={folder.id} id={folder.id}>
|
|
||||||
<div className={classes["root"]} data-selected={folder.selected.toString()}>
|
|
||||||
<div className={classes["left-side"]}>
|
|
||||||
<Typography typo={ETypo.TEXT_MD_REGULAR}>{folder.name}</Typography>
|
|
||||||
</div>
|
|
||||||
<div className={classes["right-side"]}>
|
|
||||||
{folder.hasFlag && <WarningBadge />}
|
|
||||||
{folder.rightIcon}
|
|
||||||
<Image alt="chevron" src={ChevronIcon} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -5,12 +5,13 @@ import Link from "next/link";
|
|||||||
import { NextRouter, useRouter } from "next/router";
|
import { NextRouter, useRouter } from "next/router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import BlockList, { IBlock } from "../BlockList";
|
|
||||||
import Button from "../Button";
|
import Button from "../Button";
|
||||||
import SearchBar from "../SearchBar";
|
import SearchBar from "../SearchBar";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
||||||
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
||||||
|
import { IBlock } from "../SearchBlockList/BlockList/Block";
|
||||||
|
import BlockList from "../SearchBlockList/BlockList";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
folders: OfficeFolder[];
|
folders: OfficeFolder[];
|
||||||
@ -106,9 +107,10 @@ class FolderArchivedListContainerClass extends React.Component<IPropsClass, ISta
|
|||||||
return [...pendingFolders, ...otherFolders].map((folder) => {
|
return [...pendingFolders, ...otherFolders].map((folder) => {
|
||||||
return {
|
return {
|
||||||
id: folder.uid!,
|
id: folder.uid!,
|
||||||
name: folder.folder_number! + " - " + folder.name!,
|
primaryText: folder.name,
|
||||||
selected: this.props.selectedFolder === folder.uid,
|
isActive: this.props.selectedFolder === folder.uid,
|
||||||
hasFlag: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
|
secondaryText: folder.folder_number,
|
||||||
|
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,14 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { useCallback, useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
import BlockList, { IBlock } from "../BlockList";
|
|
||||||
import Button from "../Button";
|
import Button from "../Button";
|
||||||
import SearchBar from "../SearchBar";
|
import SearchBar from "../SearchBar";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
import Rules, { RulesMode } from "@Front/Components/Elements/Rules";
|
||||||
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
import { AppRuleActions, AppRuleNames } from "@Front/Api/Entities/rule";
|
||||||
|
import { IBlock } from "../SearchBlockList/BlockList/Block";
|
||||||
|
import BlockList from "../SearchBlockList/BlockList";
|
||||||
|
import SearchBlockList from "../SearchBlockList";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
folders: OfficeFolder[];
|
folders: OfficeFolder[];
|
||||||
@ -76,9 +78,10 @@ export default function FolderListContainer(props: IProps) {
|
|||||||
return [...pendingFolders, ...otherFolders].map((folder) => {
|
return [...pendingFolders, ...otherFolders].map((folder) => {
|
||||||
return {
|
return {
|
||||||
id: folder.uid!,
|
id: folder.uid!,
|
||||||
name: folder.folder_number! + " - " + folder.name!,
|
primaryText: folder.name,
|
||||||
selected: folderUid === folder.uid,
|
secondaryText: folder.folder_number,
|
||||||
hasFlag: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
|
isActive: folderUid === folder.uid,
|
||||||
|
showAlert: folder.documents?.some((document) => document.document_status === EDocumentStatus.DEPOSITED),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -99,18 +102,18 @@ export default function FolderListContainer(props: IProps) {
|
|||||||
setBlocks(getBlocks(folders));
|
setBlocks(getBlocks(folders));
|
||||||
}, [folders, getBlocks]);
|
}, [folders, getBlocks]);
|
||||||
|
|
||||||
const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path;
|
//const navigatePath = Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path;
|
||||||
return (
|
return (
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
<div className={classes["header"]}>
|
<SearchBlockList
|
||||||
<div className={classes["searchbar"]}>
|
blocks={blocks}
|
||||||
<SearchBar onChange={filterFolders} placeholder="Chercher un dossier" />
|
onSelectedBlock={onSelectedFolder}
|
||||||
</div>
|
bottomButton={{
|
||||||
<div className={classes["folderlist-container"]}>
|
link: Module.getInstance().get().modules.pages.Folder.pages.CreateFolder.props.path,
|
||||||
<BlockList blocks={blocks} onSelectedBlock={onSelectedFolder} />
|
text: "Créer un dossier",
|
||||||
</div>
|
}}
|
||||||
</div>
|
/>
|
||||||
{!isArchived && (
|
{/* {!isArchived && (
|
||||||
<div>
|
<div>
|
||||||
<Rules
|
<Rules
|
||||||
mode={RulesMode.NECESSARY}
|
mode={RulesMode.NECESSARY}
|
||||||
@ -125,7 +128,7 @@ export default function FolderListContainer(props: IProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Rules>
|
</Rules>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
font-weight: var(--font-text-weight-semibold, 600);
|
font-weight: var(--font-text-weight-semibold, 600);
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
letter-spacing: 0.08px;
|
letter-spacing: 0.08px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: var(--input-placeholder-empty, #6d7e8a);
|
color: var(--input-placeholder-empty, #6d7e8a);
|
||||||
|
@ -12,26 +12,18 @@ export default function SearchBar({ onChange, placeholder = "Rechercher" }: IPro
|
|||||||
const [isFocused, setIsFocused] = React.useState(false);
|
const [isFocused, setIsFocused] = React.useState(false);
|
||||||
const [value, setValue] = React.useState("");
|
const [value, setValue] = React.useState("");
|
||||||
|
|
||||||
const handleOnChange = useCallback(
|
const changeValue = useCallback(
|
||||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
(value: string) => {
|
||||||
setValue(event.target.value);
|
setValue(value);
|
||||||
onChange && onChange(event.target.value);
|
onChange && onChange(value);
|
||||||
},
|
},
|
||||||
[onChange],
|
[onChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => changeValue(event.target.value);
|
||||||
setIsFocused(true);
|
const handleFocus = () => setIsFocused(true);
|
||||||
}, []);
|
const handleBlur = () => setIsFocused(false);
|
||||||
|
const clearValue = () => changeValue("");
|
||||||
const handleBlur = useCallback(() => {
|
|
||||||
setIsFocused(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const clearValue = useCallback(() => {
|
|
||||||
setValue("");
|
|
||||||
onChange && onChange("");
|
|
||||||
}, [onChange]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""}>
|
<label className={classes["root"]} data-is-focused={isFocused} data-has-value={value !== ""}>
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
.root {
|
||||||
|
background: var(--navigation-button-background-default, #f7f8f8);
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: var(--navigation-button-background-actived, #edeff1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-is-active="true"] {
|
||||||
|
border-left: 3px solid var(--navigation-border, #005bcb);
|
||||||
|
background: var(--navigation-button-background-actived, #edeff1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
padding: var(--spacing-md, 16px) var(--spacing-lg, 24px);
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm, 8px);
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--separator-stroke-light, #d7dce0);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import { ChevronRightIcon } from "@heroicons/react/24/outline";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
|
||||||
|
export type IBlock = {
|
||||||
|
id: string;
|
||||||
|
primaryText: string;
|
||||||
|
secondaryText?: string;
|
||||||
|
showAlert?: boolean;
|
||||||
|
isActive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IProps = IBlock & {
|
||||||
|
hasSeparator?: boolean;
|
||||||
|
onClick?: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Block(props: IProps) {
|
||||||
|
const { primaryText, secondaryText, showAlert = false, hasSeparator = true, isActive = false, onClick } = props;
|
||||||
|
|
||||||
|
const handleOnClick = useCallback(() => onClick && onClick(props.id), [onClick, props.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]} data-is-active={isActive} onClick={handleOnClick}>
|
||||||
|
<div className={classes["content"]}>
|
||||||
|
<div className={classes["text-container"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_LIGHT} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
|
||||||
|
{secondaryText}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_BOLD} color={ETypoColor.NAVIGATION_BUTTON_CONTRAST_DEFAULT}>
|
||||||
|
{primaryText}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
{showAlert && (
|
||||||
|
<ExclamationCircleIcon width="24" height="24" className={classes["icon"]} color={"var(--warning-default-base)"} />
|
||||||
|
)}
|
||||||
|
<ChevronRightIcon width="24" height="24" className={classes["icon"]} color={"var(--button-icon-button-default-default)"} />
|
||||||
|
</div>
|
||||||
|
{hasSeparator && <div className={classes["separator"]} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import React, { useCallback } from "react";
|
||||||
|
import Block, { IBlock } from "./Block";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
blocks: IBlock[];
|
||||||
|
onSelectedBlock: (block: IBlock) => void;
|
||||||
|
};
|
||||||
|
export default function BlockList({ blocks, onSelectedBlock }: IProps) {
|
||||||
|
const selectBlock = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
onSelectedBlock && onSelectedBlock(blocks.find((folder) => folder.id === id)!);
|
||||||
|
},
|
||||||
|
[blocks, onSelectedBlock],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{blocks.map((block) => {
|
||||||
|
return <Block {...block} hasSeparator onClick={selectBlock} key={block.id} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
.root {
|
||||||
|
width: 336px;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
border-radius: var(--navigation-radius, 0px);
|
||||||
|
background: var(--navigation-button-background-default, #f7f8f8);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.block-list {
|
||||||
|
overflow-y: auto;
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar {
|
||||||
|
padding: var(--spacing-md, 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 336px;
|
||||||
|
}
|
||||||
|
}
|
69
src/front/Components/DesignSystem/SearchBlockList/index.tsx
Normal file
69
src/front/Components/DesignSystem/SearchBlockList/index.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import BlockList from "./BlockList";
|
||||||
|
import { IBlock } from "./BlockList/Block";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import SearchBar from "../SearchBar";
|
||||||
|
import Button from "../Button";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
blocks: IBlock[];
|
||||||
|
onSelectedBlock: (block: IBlock) => void;
|
||||||
|
bottomButton?: {
|
||||||
|
text: string;
|
||||||
|
link: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default function SearchBlockList(props: IProps) {
|
||||||
|
const { blocks, onSelectedBlock, bottomButton } = props;
|
||||||
|
|
||||||
|
const [blocksShowing, setBlocksShowing] = useState<IBlock[]>(blocks);
|
||||||
|
const bottomButtonRef = useRef<HTMLAnchorElement>(null);
|
||||||
|
const searchBarRef = useRef<HTMLDivElement>(null);
|
||||||
|
const handleSearchChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
const filteredBlocks = blocks.filter((block) => {
|
||||||
|
const name = block.primaryText.toLowerCase();
|
||||||
|
const number = block.secondaryText?.toLowerCase();
|
||||||
|
value = value.toLowerCase();
|
||||||
|
return name.includes(value) || number?.includes(value);
|
||||||
|
});
|
||||||
|
setBlocksShowing(filteredBlocks);
|
||||||
|
},
|
||||||
|
[blocks],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getHeight = useCallback(() => {
|
||||||
|
let heightToSubstract = 0;
|
||||||
|
if (bottomButton && bottomButtonRef.current) {
|
||||||
|
heightToSubstract += bottomButtonRef.current.clientHeight;
|
||||||
|
}
|
||||||
|
if (searchBarRef.current) {
|
||||||
|
heightToSubstract += searchBarRef.current.clientHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
maxHeight: `calc(100% - ${heightToSubstract}px)`,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setBlocksShowing(blocks);
|
||||||
|
}, [blocks]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<div className={classes["searchbar"]} ref={searchBarRef}>
|
||||||
|
<SearchBar placeholder="Chercher" onChange={handleSearchChange} />
|
||||||
|
</div>
|
||||||
|
<div className={classes["block-list"]} style={getHeight()}>
|
||||||
|
<BlockList blocks={blocksShowing} onSelectedBlock={onSelectedBlock} />
|
||||||
|
</div>
|
||||||
|
{bottomButton && (
|
||||||
|
<Link href={bottomButton.link} className={classes["bottom-container"]} ref={bottomButtonRef}>
|
||||||
|
<Button fullwidth>{bottomButton.text}</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -28,25 +28,25 @@ export enum ETypo {
|
|||||||
TEXT_LG_SEMIBOLD = "text-lg-semibold",
|
TEXT_LG_SEMIBOLD = "text-lg-semibold",
|
||||||
TEXT_LG_REGULAR = "text-lg-regular",
|
TEXT_LG_REGULAR = "text-lg-regular",
|
||||||
TEXT_LG_UPPERCASE = "text-lg-uppercase",
|
TEXT_LG_UPPERCASE = "text-lg-uppercase",
|
||||||
TEXT_LG_light = "text-lg-light",
|
TEXT_LG_LIGHT = "text-lg-light",
|
||||||
|
|
||||||
TEXT_MD_BOLD = "text-md-bold",
|
TEXT_MD_BOLD = "text-md-bold",
|
||||||
TEXT_MD_SEMIBOLD = "text-md-semibold",
|
TEXT_MD_SEMIBOLD = "text-md-semibold",
|
||||||
TEXT_MD_REGULAR = "text-md-regular",
|
TEXT_MD_REGULAR = "text-md-regular",
|
||||||
TEXT_MD_UPPERCASE = "text-md-uppercase",
|
TEXT_MD_UPPERCASE = "text-md-uppercase",
|
||||||
TEXT_MD_light = "text-md-light",
|
TEXT_MD_LIGHT = "text-md-light",
|
||||||
|
|
||||||
TEXT_SM_BOLD = "text-sm-bold",
|
TEXT_SM_BOLD = "text-sm-bold",
|
||||||
TEXT_SM_SEMIBOLD = "text-sm-semibold",
|
TEXT_SM_SEMIBOLD = "text-sm-semibold",
|
||||||
TEXT_SM_REGULAR = "text-sm-regular",
|
TEXT_SM_REGULAR = "text-sm-regular",
|
||||||
TEXT_SM_UPPERCASE = "text-sm-uppercase",
|
TEXT_SM_UPPERCASE = "text-sm-uppercase",
|
||||||
TEXT_SM_light = "text-sm-light",
|
TEXT_SM_LIGHT = "text-sm-light",
|
||||||
|
|
||||||
TEXT_XS_BOLD = "text-xs-bold",
|
TEXT_XS_BOLD = "text-xs-bold",
|
||||||
TEXT_XS_SEMIBOLD = "text-xs-semibold",
|
TEXT_XS_SEMIBOLD = "text-xs-semibold",
|
||||||
TEXT_XS_REGULAR = "text-xs-regular",
|
TEXT_XS_REGULAR = "text-xs-regular",
|
||||||
TEXT_XS_UPPERCASE = "text-xs-uppercase",
|
TEXT_XS_UPPERCASE = "text-xs-uppercase",
|
||||||
TEXT_XS_light = "text-xs-light",
|
TEXT_XS_LIGHT = "text-xs-light",
|
||||||
|
|
||||||
CAPTION_BOLD = "caption-bold",
|
CAPTION_BOLD = "caption-bold",
|
||||||
CAPTION_SEMIBOLD = "caption-semibold",
|
CAPTION_SEMIBOLD = "caption-semibold",
|
||||||
@ -153,6 +153,7 @@ export enum ETypoColor {
|
|||||||
CONTRAST_DEFAULT = "--contrast-default",
|
CONTRAST_DEFAULT = "--contrast-default",
|
||||||
CONTRAST_HOVERED = "--contrast-hovered",
|
CONTRAST_HOVERED = "--contrast-hovered",
|
||||||
ERROR_WEAK_CONTRAST = "--error-weak-contrast",
|
ERROR_WEAK_CONTRAST = "--error-weak-contrast",
|
||||||
|
NAVIGATION_BUTTON_CONTRAST_DEFAULT = "--navigation-button-contrast-default",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Typography(props: IProps) {
|
export default function Typography(props: IProps) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user