mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-22 19:41:53 +03:00
Scrollable fixed dropdowns container minor refactor (#9159)
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
parent
94136d953e
commit
54c4d64ae8
@ -1,8 +1,3 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { PositionType } from '../types/PositionType';
|
||||
|
||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
|
||||
@ -13,7 +8,10 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
import { PositionType } from '../types/PositionType';
|
||||
|
||||
type StyledContainerProps = {
|
||||
position: PositionType;
|
||||
|
@ -99,6 +99,7 @@ export const FavoriteFolderPicker = ({
|
||||
toggleFolderSelection={toggleFolderSelection}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<FavoriteFolderPickerFooter dropdownId={dropdownId} />
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
@ -4,16 +4,9 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { IconPlus, MenuItem } from 'twenty-ui';
|
||||
|
||||
const StyledFooter = styled.div`
|
||||
border-bottom-left-radius: ${({ theme }) => theme.border.radius.md};
|
||||
border-bottom-right-radius: ${({ theme }) => theme.border.radius.md};
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
export const FavoriteFolderPickerFooter = ({
|
||||
dropdownId,
|
||||
}: {
|
||||
@ -30,20 +23,18 @@ export const FavoriteFolderPickerFooter = ({
|
||||
const { closeDropdown } = useDropdown(dropdownId);
|
||||
|
||||
return (
|
||||
<StyledFooter>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
className="add-folder"
|
||||
onClick={() => {
|
||||
setIsNavigationDrawerExpanded(true);
|
||||
openNavigationSection();
|
||||
setIsFavoriteFolderCreating(true);
|
||||
closeDropdown();
|
||||
}}
|
||||
text="Add folder"
|
||||
LeftIcon={() => <IconPlus size={theme.icon.size.md} />}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledFooter>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
className="add-folder"
|
||||
onClick={() => {
|
||||
setIsNavigationDrawerExpanded(true);
|
||||
openNavigationSection();
|
||||
setIsFavoriteFolderCreating(true);
|
||||
closeDropdown();
|
||||
}}
|
||||
text="Add folder"
|
||||
LeftIcon={() => <IconPlus size={theme.icon.size.md} />}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
);
|
||||
};
|
||||
|
@ -62,7 +62,7 @@ export const ObjectOptionsDropdownFieldsContent = () => {
|
||||
showDragGrip={true}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItemNavigate
|
||||
onClick={() => onContentChange('hiddenFields')}
|
||||
LeftIcon={IconEyeOff}
|
||||
|
@ -87,7 +87,7 @@ export const ObjectOptionsDropdownHiddenFieldsContent = () => {
|
||||
closeDropdown();
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem LeftIcon={IconSettings} text="Edit Fields" />
|
||||
</DropdownMenuItemsContainer>
|
||||
</UndecoratedLink>
|
||||
|
@ -98,7 +98,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
{/** TODO: Should be removed when view settings contains more options */}
|
||||
{viewType === ViewType.Kanban && (
|
||||
<>
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('viewSettings')}
|
||||
LeftIcon={IconLayout}
|
||||
@ -109,7 +109,7 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('fields')}
|
||||
LeftIcon={IconTag}
|
||||
|
@ -53,7 +53,7 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<UndecoratedLink
|
||||
fullWidth
|
||||
to={`/settings/objects/${getObjectSlug(objectMetadataItem)}`}
|
||||
|
@ -144,7 +144,7 @@ export const MultiRecordSelect = ({
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
{isDefined(onCreate) && (
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
@ -181,7 +181,7 @@ export const MultiRecordSelect = ({
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{isDefined(onCreate) && (
|
||||
<DropdownMenuItemsContainer withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
|
@ -69,7 +69,7 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
<>
|
||||
{dropdownPlacement?.includes('end') && (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
{records.recordsToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
@ -117,7 +117,7 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{isDefined(onCreate) && (
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
{createNewButton}
|
||||
</DropdownMenuItemsContainer>
|
||||
)}
|
||||
|
@ -8,7 +8,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
ColorSample,
|
||||
IconCheck,
|
||||
@ -21,7 +20,6 @@ import {
|
||||
MenuItem,
|
||||
MenuItemSelectColor,
|
||||
} from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
import { computeOptionValueFromLabel } from '~/pages/settings/data-model/utils/compute-option-value-from-label.utils';
|
||||
|
||||
type SettingsDataModelFieldSelectFormOptionRowProps = {
|
||||
@ -81,17 +79,14 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const dropdownIds = useMemo(() => {
|
||||
const baseScopeId = `select-field-option-row-${v4()}`;
|
||||
return {
|
||||
color: `${baseScopeId}-color`,
|
||||
actions: `${baseScopeId}-actions`,
|
||||
};
|
||||
}, []);
|
||||
const SELECT_COLOR_DROPDOWN_ID = 'select-color-dropdown';
|
||||
const SELECT_ACTIONS_DROPDOWN_ID = 'select-actions-dropdown';
|
||||
|
||||
const { closeDropdown: closeColorDropdown } = useDropdown(dropdownIds.color);
|
||||
const { closeDropdown: closeColorDropdown } = useDropdown(
|
||||
SELECT_COLOR_DROPDOWN_ID,
|
||||
);
|
||||
const { closeDropdown: closeActionsDropdown } = useDropdown(
|
||||
dropdownIds.actions,
|
||||
SELECT_ACTIONS_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const handleInputEnter = () => {
|
||||
@ -120,28 +115,26 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
/>
|
||||
</AdvancedSettingsWrapper>
|
||||
<Dropdown
|
||||
dropdownId={dropdownIds.color}
|
||||
dropdownId={SELECT_COLOR_DROPDOWN_ID}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownIds.color,
|
||||
scope: SELECT_COLOR_DROPDOWN_ID,
|
||||
}}
|
||||
clickableComponent={<StyledColorSample colorName={option.color} />}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
{MAIN_COLOR_NAMES.map((colorName) => (
|
||||
<MenuItemSelectColor
|
||||
key={colorName}
|
||||
onClick={() => {
|
||||
onChange({ ...option, color: colorName });
|
||||
closeColorDropdown();
|
||||
}}
|
||||
color={colorName}
|
||||
selected={colorName === option.color}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
{MAIN_COLOR_NAMES.map((colorName) => (
|
||||
<MenuItemSelectColor
|
||||
key={colorName}
|
||||
onClick={() => {
|
||||
onChange({ ...option, color: colorName });
|
||||
closeColorDropdown();
|
||||
}}
|
||||
color={colorName}
|
||||
selected={colorName === option.color}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
/>
|
||||
<StyledOptionInput
|
||||
@ -165,10 +158,10 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
autoSelectOnMount={isNewRow}
|
||||
/>
|
||||
<Dropdown
|
||||
dropdownId={dropdownIds.actions}
|
||||
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
|
||||
dropdownPlacement="right-start"
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownIds.actions,
|
||||
scope: SELECT_ACTIONS_DROPDOWN_ID,
|
||||
}}
|
||||
clickableComponent={
|
||||
<StyledLightIconButton accent="tertiary" Icon={IconDotsVertical} />
|
||||
|
@ -16,7 +16,6 @@ import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
|
||||
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag';
|
||||
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
|
||||
@ -86,21 +85,20 @@ export const SettingsObjectSummaryCard = ({
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownMenuWidth={160}
|
||||
dropdownComponents={
|
||||
<DropdownMenu width="160px">
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Edit"
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Deactivate"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDeactivate}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text="Edit"
|
||||
LeftIcon={IconPencil}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text="Deactivate"
|
||||
LeftIcon={IconArchive}
|
||||
onClick={handleDeactivate}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownId,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
|
||||
import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
@ -64,18 +63,16 @@ export const SettingsIntegrationDatabaseConnectionSummaryCard = ({
|
||||
<LightIconButton Icon={IconDotsVertical} accent="tertiary" />
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Remove"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
<UndecoratedLink to="./edit">
|
||||
<MenuItem LeftIcon={IconPencil} text="Edit" />
|
||||
</UndecoratedLink>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconTrash}
|
||||
text="Remove"
|
||||
onClick={onRemove}
|
||||
/>
|
||||
<UndecoratedLink to="./edit">
|
||||
<MenuItem LeftIcon={IconPencil} text="Edit" />
|
||||
</UndecoratedLink>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
@ -165,7 +165,7 @@ export const Select = <Value extends SelectValue>({
|
||||
<DropdownMenuSeparator />
|
||||
)}
|
||||
{!!callToActionButton && (
|
||||
<DropdownMenuItemsContainer hasMaxHeight withoutScrollWrapper>
|
||||
<DropdownMenuItemsContainer hasMaxHeight scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={callToActionButton.onClick}
|
||||
LeftIcon={callToActionButton.Icon}
|
||||
|
@ -38,16 +38,16 @@ export const DropdownMenuItemsContainer = ({
|
||||
children,
|
||||
hasMaxHeight,
|
||||
className,
|
||||
withoutScrollWrapper,
|
||||
scrollable = true,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
hasMaxHeight?: boolean;
|
||||
className?: string;
|
||||
withoutScrollWrapper?: boolean;
|
||||
scrollable?: boolean;
|
||||
}) => {
|
||||
const id = useId();
|
||||
|
||||
return withoutScrollWrapper === true ? (
|
||||
return scrollable !== true ? (
|
||||
<StyledDropdownMenuItemsExternalContainer
|
||||
hasMaxHeight={hasMaxHeight}
|
||||
className={className}
|
||||
|
@ -19,7 +19,6 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { Dropdown } from '../../dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
z-index: 1;
|
||||
@ -93,22 +92,20 @@ export const ShowPageAddButton = ({
|
||||
)
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => handleSelect(CoreObjectNameSingular.Note)}
|
||||
accent="default"
|
||||
LeftIcon={IconNotes}
|
||||
text="Note"
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => handleSelect(CoreObjectNameSingular.Task)}
|
||||
accent="default"
|
||||
LeftIcon={IconCheckbox}
|
||||
text="Task"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => handleSelect(CoreObjectNameSingular.Note)}
|
||||
accent="default"
|
||||
LeftIcon={IconNotes}
|
||||
text="Note"
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={() => handleSelect(CoreObjectNameSingular.Task)}
|
||||
accent="default"
|
||||
LeftIcon={IconCheckbox}
|
||||
text="Task"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: PageHotkeyScope.ShowPage,
|
||||
|
@ -7,13 +7,13 @@ import { SummaryCard } from '@/object-record/record-show/components/SummaryCard'
|
||||
import { RecordLayout } from '@/object-record/record-show/types/RecordLayout';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||
|
||||
const StyledShowPageRightContainer = styled.div<{ isMobile: boolean }>`
|
||||
display: flex;
|
||||
|
@ -112,15 +112,13 @@ export const UpdateViewButtonGroup = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleCreateViewClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Create view"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleCreateViewClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Create view"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
@ -190,7 +190,7 @@ export const ViewPickerContentCreateMode = () => {
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<ViewPickerSaveButtonContainer>
|
||||
<ViewPickerCreateButton />
|
||||
</ViewPickerSaveButtonContainer>
|
||||
|
@ -89,7 +89,7 @@ export const ViewPickerContentEditMode = () => {
|
||||
</ViewPickerIconAndNameContainer>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<ViewPickerSaveButtonContainer>
|
||||
<ViewPickerEditButton />
|
||||
</ViewPickerSaveButtonContainer>
|
||||
|
@ -97,7 +97,7 @@ export const ViewPickerListContent = () => {
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<StyledBoldDropdownMenuItemsContainer>
|
||||
<StyledBoldDropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={handleAddViewButtonClick}
|
||||
LeftIcon={IconPlus}
|
||||
|
Loading…
Reference in New Issue
Block a user