Added tooltip on overflowing texts (#771)
* Ok * Fixes * Fix according to PR * Fix lint --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -18,6 +18,8 @@ import {
|
|||||||
beautifyPastDateRelativeToNow,
|
beautifyPastDateRelativeToNow,
|
||||||
} from '~/utils/date-utils';
|
} from '~/utils/date-utils';
|
||||||
|
|
||||||
|
import { OverflowingTextWithTooltip } from '../../../ui/tooltip/OverflowingTextWithTooltip';
|
||||||
|
|
||||||
const StyledMainContainer = styled.div`
|
const StyledMainContainer = styled.div`
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
@ -144,17 +146,15 @@ const StyledCardTitle = styled.div`
|
|||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCardContent = styled.div`
|
const StyledCardContent = styled.div`
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
display: -webkit-box;
|
|
||||||
overflow: hidden;
|
width: 100%;
|
||||||
text-overflow: ellipsis;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTooltip = styled(Tooltip)`
|
const StyledTooltip = styled(Tooltip)`
|
||||||
@ -279,10 +279,18 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledCardTitle>
|
<StyledCardTitle>
|
||||||
{commentThread.title ? commentThread.title : '(No title)'}
|
<OverflowingTextWithTooltip
|
||||||
|
text={
|
||||||
|
commentThread.title
|
||||||
|
? commentThread.title
|
||||||
|
: '(No title)'
|
||||||
|
}
|
||||||
|
/>
|
||||||
</StyledCardTitle>
|
</StyledCardTitle>
|
||||||
<StyledCardContent>
|
<StyledCardContent>
|
||||||
{body ? body : '(No content)'}
|
<OverflowingTextWithTooltip
|
||||||
|
text={body ? body : '(No content)'}
|
||||||
|
/>
|
||||||
</StyledCardContent>
|
</StyledCardContent>
|
||||||
</StyledCard>
|
</StyledCard>
|
||||||
</StyledCardContainer>
|
</StyledCardContainer>
|
||||||
|
|||||||
@ -45,6 +45,7 @@ const StyledBoardCard = styled.div<{ selected: boolean }>`
|
|||||||
|
|
||||||
const StyledBoardCardWrapper = styled.div`
|
const StyledBoardCardWrapper = styled.div`
|
||||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledBoardCardHeader = styled.div`
|
const StyledBoardCardHeader = styled.div`
|
||||||
@ -64,6 +65,7 @@ const StyledBoardCardHeader = styled.div`
|
|||||||
width: ${({ theme }) => theme.icon.size.md}px;
|
width: ${({ theme }) => theme.icon.size.md}px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledBoardCardBody = styled.div`
|
const StyledBoardCardBody = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -12,7 +12,9 @@ export const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
|||||||
isFirstColumn ? 'none' : theme.border.color.light};
|
isFirstColumn ? 'none' : theme.border.color.light};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
max-width: 200px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { Avatar, AvatarType } from '@/users/components/Avatar';
|
import { Avatar, AvatarType } from '@/users/components/Avatar';
|
||||||
|
|
||||||
|
import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip';
|
||||||
|
|
||||||
export enum ChipVariant {
|
export enum ChipVariant {
|
||||||
opaque = 'opaque',
|
opaque = 'opaque',
|
||||||
transparent = 'transparent',
|
transparent = 'transparent',
|
||||||
@ -94,7 +96,9 @@ export function EntityChip({
|
|||||||
size={14}
|
size={14}
|
||||||
type={avatarType}
|
type={avatarType}
|
||||||
/>
|
/>
|
||||||
<StyledName>{name}</StyledName>
|
<StyledName>
|
||||||
|
<OverflowingTextWithTooltip text={name} />
|
||||||
|
</StyledName>
|
||||||
</StyledContainerLink>
|
</StyledContainerLink>
|
||||||
) : (
|
) : (
|
||||||
<StyledContainerReadOnly data-testid="entity-chip">
|
<StyledContainerReadOnly data-testid="entity-chip">
|
||||||
@ -105,7 +109,9 @@ export function EntityChip({
|
|||||||
size={14}
|
size={14}
|
||||||
type={avatarType}
|
type={avatarType}
|
||||||
/>
|
/>
|
||||||
<StyledName>{name}</StyledName>
|
<StyledName>
|
||||||
|
<OverflowingTextWithTooltip text={name} />
|
||||||
|
</StyledName>
|
||||||
</StyledContainerReadOnly>
|
</StyledContainerReadOnly>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ const DropdownMenuSelectableItemContainer = styled(DropdownMenuItem)<Props>`
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
max-width: 150px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledLeftContainer = styled.div`
|
const StyledLeftContainer = styled.div`
|
||||||
@ -30,6 +32,8 @@ const StyledLeftContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledRightIcon = styled.div`
|
const StyledRightIcon = styled.div`
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export function NumberEditableField({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
displayModeContent={internalValue}
|
displayModeContent={internalValue}
|
||||||
isDisplayModeContentEmpty={!(internalValue !== '')}
|
isDisplayModeContentEmpty={!(internalValue !== '' && internalValue)}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
|||||||
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
|
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
|
||||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
|
import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -54,7 +56,7 @@ export function TextEditableField({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
displayModeContent={internalValue}
|
displayModeContent={<OverflowingTextWithTooltip text={internalValue} />}
|
||||||
isDisplayModeContentEmpty={!(internalValue !== '')}
|
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import {
|
|||||||
beautifyPastDateRelativeToNow,
|
beautifyPastDateRelativeToNow,
|
||||||
} from '~/utils/date-utils';
|
} from '~/utils/date-utils';
|
||||||
|
|
||||||
|
import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
id?: string;
|
id?: string;
|
||||||
logoOrAvatar?: string;
|
logoOrAvatar?: string;
|
||||||
@ -31,6 +33,7 @@ const StyledInfoContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDate = styled.div`
|
const StyledDate = styled.div`
|
||||||
@ -42,6 +45,8 @@ const StyledTitle = styled.div`
|
|||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
font-size: ${({ theme }) => theme.font.size.xl};
|
font-size: ${({ theme }) => theme.font.size.xl};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTooltip = styled(Tooltip)`
|
const StyledTooltip = styled(Tooltip)`
|
||||||
@ -50,6 +55,8 @@ const StyledTooltip = styled(Tooltip)`
|
|||||||
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -74,7 +81,9 @@ export function ShowPageSummaryCard({
|
|||||||
placeholder={title}
|
placeholder={title}
|
||||||
/>
|
/>
|
||||||
<StyledInfoContainer>
|
<StyledInfoContainer>
|
||||||
<StyledTitle>{title}</StyledTitle>
|
<StyledTitle>
|
||||||
|
<OverflowingTextWithTooltip text={title} />
|
||||||
|
</StyledTitle>
|
||||||
<StyledDate id={dateElementId}>
|
<StyledDate id={dateElementId}>
|
||||||
Added {beautifiedCreatedAt} ago
|
Added {beautifiedCreatedAt} ago
|
||||||
</StyledDate>
|
</StyledDate>
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { IconButton } from '@/ui/button/components/IconButton';
|
|||||||
import { IconChevronLeft, IconPlus } from '@/ui/icon/index';
|
import { IconChevronLeft, IconPlus } from '@/ui/icon/index';
|
||||||
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
|
import NavCollapseButton from '@/ui/navbar/components/NavCollapseButton';
|
||||||
|
|
||||||
|
import { OverflowingTextWithTooltip } from '../../../tooltip/OverflowingTextWithTooltip';
|
||||||
|
|
||||||
export const TOP_BAR_MIN_HEIGHT = 40;
|
export const TOP_BAR_MIN_HEIGHT = 40;
|
||||||
|
|
||||||
const TopBarContainer = styled.div`
|
const TopBarContainer = styled.div`
|
||||||
@ -14,18 +16,25 @@ const TopBarContainer = styled.div`
|
|||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-size: 14px;
|
font-size: ${({ theme }) => theme.font.size.lg};
|
||||||
|
justify-content: space-between;
|
||||||
min-height: ${TOP_BAR_MIN_HEIGHT}px;
|
min-height: ${TOP_BAR_MIN_HEIGHT}px;
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledLeftContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
const TitleContainer = styled.div`
|
const TitleContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
font-family: 'Inter';
|
font-size: ${({ theme }) => theme.font.size.md};
|
||||||
font-size: 14px;
|
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||||
margin-left: 4px;
|
max-width: 50%;
|
||||||
width: 100%;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BackIconButton = styled(IconButton)`
|
const BackIconButton = styled(IconButton)`
|
||||||
@ -51,15 +60,19 @@ export function TopBar({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopBarContainer>
|
<TopBarContainer>
|
||||||
<NavCollapseButton hideIfOpen={true} />
|
<StyledLeftContainer>
|
||||||
{hasBackButton && (
|
<NavCollapseButton hideIfOpen={true} />
|
||||||
<BackIconButton
|
{hasBackButton && (
|
||||||
icon={<IconChevronLeft size={16} />}
|
<BackIconButton
|
||||||
onClick={navigateBack}
|
icon={<IconChevronLeft size={16} />}
|
||||||
/>
|
onClick={navigateBack}
|
||||||
)}
|
/>
|
||||||
{icon}
|
)}
|
||||||
<TitleContainer data-testid="top-bar-title">{title}</TitleContainer>
|
{icon}
|
||||||
|
<TitleContainer data-testid="top-bar-title">
|
||||||
|
<OverflowingTextWithTooltip text={title} />
|
||||||
|
</TitleContainer>
|
||||||
|
</StyledLeftContainer>
|
||||||
{onAddButtonClick && (
|
{onAddButtonClick && (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={16} />}
|
icon={<IconPlus size={16} />}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
|||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
|
import { OverflowingTextWithTooltip } from '../../tooltip/OverflowingTextWithTooltip';
|
||||||
import { useEntitySelectScroll } from '../hooks/useEntitySelectScroll';
|
import { useEntitySelectScroll } from '../hooks/useEntitySelectScroll';
|
||||||
import { EntityForSelect } from '../types/EntityForSelect';
|
import { EntityForSelect } from '../types/EntityForSelect';
|
||||||
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
|
||||||
@ -86,7 +87,7 @@ export function SingleEntitySelectBase<
|
|||||||
size={16}
|
size={16}
|
||||||
type={entity.avatarType ?? 'rounded'}
|
type={entity.avatarType ?? 'rounded'}
|
||||||
/>
|
/>
|
||||||
{entity.name}
|
<OverflowingTextWithTooltip text={entity.name} />
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export const borderLight = {
|
|||||||
strong: grayScale.gray25,
|
strong: grayScale.gray25,
|
||||||
medium: grayScale.gray20,
|
medium: grayScale.gray20,
|
||||||
light: grayScale.gray15,
|
light: grayScale.gray15,
|
||||||
|
invertedSecondary: grayScale.gray50,
|
||||||
inverted: grayScale.gray60,
|
inverted: grayScale.gray60,
|
||||||
},
|
},
|
||||||
...common,
|
...common,
|
||||||
@ -24,6 +25,7 @@ export const borderDark = {
|
|||||||
strong: grayScale.gray65,
|
strong: grayScale.gray65,
|
||||||
medium: grayScale.gray70,
|
medium: grayScale.gray70,
|
||||||
light: grayScale.gray75,
|
light: grayScale.gray75,
|
||||||
|
invertedSecondary: grayScale.gray40,
|
||||||
inverted: grayScale.gray30,
|
inverted: grayScale.gray30,
|
||||||
},
|
},
|
||||||
...common,
|
...common,
|
||||||
|
|||||||
18
front/src/modules/ui/tooltip/AppTooltip.tsx
Normal file
18
front/src/modules/ui/tooltip/AppTooltip.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
export const AppTooltip = styled(Tooltip)`
|
||||||
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
|
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||||
|
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
max-width: 40%;
|
||||||
|
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||||
|
`;
|
||||||
81
front/src/modules/ui/tooltip/OverflowingTextWithTooltip.tsx
Normal file
81
front/src/modules/ui/tooltip/OverflowingTextWithTooltip.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
|
||||||
|
import { AppTooltip } from './AppTooltip';
|
||||||
|
|
||||||
|
const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>`
|
||||||
|
cursor: ${({ cursorPointer }) => (cursorPointer ? 'pointer' : 'inherit')};
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
|
||||||
|
font-weight: inherit;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function OverflowingTextWithTooltip({
|
||||||
|
text,
|
||||||
|
}: {
|
||||||
|
text: string | null | undefined;
|
||||||
|
}) {
|
||||||
|
const textElementId = `title-id-${uuidV4()}`;
|
||||||
|
|
||||||
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const [isTitleOverflowing, setIsTitleOverflowing] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isOverflowing =
|
||||||
|
(text?.length ?? 0) > 0 && textRef.current
|
||||||
|
? textRef.current?.scrollHeight > textRef.current?.clientHeight ||
|
||||||
|
textRef.current.scrollWidth > textRef.current.clientWidth
|
||||||
|
: false;
|
||||||
|
|
||||||
|
if (isTitleOverflowing !== isOverflowing) {
|
||||||
|
setIsTitleOverflowing(isOverflowing);
|
||||||
|
}
|
||||||
|
}, [isTitleOverflowing, text]);
|
||||||
|
|
||||||
|
function handleTooltipClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTooltipMouseUp(event: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledOverflowingText
|
||||||
|
ref={textRef}
|
||||||
|
id={textElementId}
|
||||||
|
cursorPointer={isTitleOverflowing}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</StyledOverflowingText>
|
||||||
|
{isTitleOverflowing &&
|
||||||
|
createPortal(
|
||||||
|
<div onMouseUp={handleTooltipMouseUp} onClick={handleTooltipClick}>
|
||||||
|
<AppTooltip
|
||||||
|
anchorSelect={`#${textElementId}`}
|
||||||
|
content={text ?? ''}
|
||||||
|
clickable
|
||||||
|
delayHide={100}
|
||||||
|
offset={5}
|
||||||
|
noArrow
|
||||||
|
place="bottom"
|
||||||
|
positionStrategy="absolute"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user