feat: colored avatar (#554)
* feat: colored avatar * fix: use id instead of name & remove unused * fix: remove unused * Allow empty ID to avoid empty string * Fix tests * Add person chip story --------- Co-authored-by: Emilien <emilien.chauvet.enpc@gmail.com>
This commit is contained in:
@ -10,7 +10,7 @@ import {
|
|||||||
|
|
||||||
import { useHandleCheckableCommentThreadTargetChange } from '@/comments/hooks/useHandleCheckableCommentThreadTargetChange';
|
import { useHandleCheckableCommentThreadTargetChange } from '@/comments/hooks/useHandleCheckableCommentThreadTargetChange';
|
||||||
import { CommentableEntityForSelect } from '@/comments/types/CommentableEntityForSelect';
|
import { CommentableEntityForSelect } from '@/comments/types/CommentableEntityForSelect';
|
||||||
import CompanyChip from '@/companies/components/CompanyChip';
|
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||||
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys';
|
||||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||||
import { PersonChip } from '@/people/components/PersonChip';
|
import { PersonChip } from '@/people/components/PersonChip';
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import {
|
|||||||
beautifyExactDate,
|
beautifyExactDate,
|
||||||
beautifyPastDateRelativeToNow,
|
beautifyPastDateRelativeToNow,
|
||||||
} from '@/utils/datetime/date-utils';
|
} from '@/utils/datetime/date-utils';
|
||||||
import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString';
|
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
comment: Pick<CommentForDrawer, 'id' | 'author' | 'createdAt'>;
|
comment: Pick<CommentForDrawer, 'id' | 'author' | 'createdAt'>;
|
||||||
@ -74,17 +73,14 @@ export function CommentHeader({ comment, actionBar }: OwnProps) {
|
|||||||
const avatarUrl = author.avatarUrl;
|
const avatarUrl = author.avatarUrl;
|
||||||
const commentId = comment.id;
|
const commentId = comment.id;
|
||||||
|
|
||||||
const capitalizedFirstUsernameLetter = isNonEmptyString(authorName)
|
|
||||||
? authorName.toLocaleUpperCase()[0]
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledLeftContainer>
|
<StyledLeftContainer>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
size={theme.icon.size.md}
|
size={theme.icon.size.md}
|
||||||
placeholder={capitalizedFirstUsernameLetter}
|
colorId={author.id}
|
||||||
|
placeholder={author.displayName}
|
||||||
/>
|
/>
|
||||||
<StyledName>{authorName}</StyledName>
|
<StyledName>{authorName}</StyledName>
|
||||||
{showDate && (
|
{showDate && (
|
||||||
|
|||||||
@ -16,7 +16,10 @@ export function CompanyAccountOwnerCell({ company }: OwnProps) {
|
|||||||
editModeContent={<CompanyAccountOwnerPicker company={company} />}
|
editModeContent={<CompanyAccountOwnerPicker company={company} />}
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
company.accountOwner?.displayName ? (
|
company.accountOwner?.displayName ? (
|
||||||
<PersonChip name={company.accountOwner?.displayName ?? ''} />
|
<PersonChip
|
||||||
|
id={company.accountOwner.id}
|
||||||
|
name={company.accountOwner?.displayName ?? ''}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import styled from '@emotion/styled';
|
|||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
|
|
||||||
export type CompanyChipPropsType = {
|
export type CompanyChipPropsType = {
|
||||||
id?: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture?: string;
|
picture?: string;
|
||||||
};
|
};
|
||||||
@ -50,7 +50,7 @@ const StyledContainerNoLink = styled.div`
|
|||||||
${baseStyle}
|
${baseStyle}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function CompanyChip({ id, name, picture }: CompanyChipPropsType) {
|
export function CompanyChip({ id, name, picture }: CompanyChipPropsType) {
|
||||||
const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink;
|
const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -58,6 +58,7 @@ function CompanyChip({ id, name, picture }: CompanyChipPropsType) {
|
|||||||
{picture && (
|
{picture && (
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={picture?.toString()}
|
avatarUrl={picture?.toString()}
|
||||||
|
colorId={id}
|
||||||
placeholder={name}
|
placeholder={name}
|
||||||
type="squared"
|
type="squared"
|
||||||
size={14}
|
size={14}
|
||||||
@ -67,5 +68,3 @@ function CompanyChip({ id, name, picture }: CompanyChipPropsType) {
|
|||||||
</ContainerComponent>
|
</ContainerComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CompanyChip;
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
useUpdateCompanyMutation,
|
useUpdateCompanyMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
import CompanyChip from './CompanyChip';
|
import { CompanyChip } from './CompanyChip';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
company: Pick<
|
company: Pick<
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
import CompanyChip from '../CompanyChip';
|
import { CompanyChip } from '../CompanyChip';
|
||||||
|
|
||||||
const meta: Meta<typeof CompanyChip> = {
|
const meta: Meta<typeof CompanyChip> = {
|
||||||
title: 'Modules/Companies/CompanyChip',
|
title: 'Modules/Companies/CompanyChip',
|
||||||
@ -32,10 +33,13 @@ const TestCellContainer = styled.div`
|
|||||||
export const SmallName: Story = {
|
export const SmallName: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(
|
||||||
<TestCellContainer>
|
<TestCellContainer>
|
||||||
<CompanyChip
|
<BrowserRouter>
|
||||||
name="Airbnb"
|
<CompanyChip
|
||||||
picture="https://api.faviconkit.com/airbnb.com/144"
|
id="airbnb"
|
||||||
/>
|
name="Airbnb"
|
||||||
|
picture="https://api.faviconkit.com/airbnb.com/144"
|
||||||
|
/>
|
||||||
|
</BrowserRouter>
|
||||||
</TestCellContainer>,
|
</TestCellContainer>,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@ -43,10 +47,13 @@ export const SmallName: Story = {
|
|||||||
export const BigName: Story = {
|
export const BigName: Story = {
|
||||||
render: getRenderWrapperForComponent(
|
render: getRenderWrapperForComponent(
|
||||||
<TestCellContainer>
|
<TestCellContainer>
|
||||||
<CompanyChip
|
<BrowserRouter>
|
||||||
name="Google with a real big name to overflow the cell"
|
<CompanyChip
|
||||||
picture="https://api.faviconkit.com/google.com/144"
|
id="google"
|
||||||
/>
|
name="Google with a real big name to overflow the cell"
|
||||||
|
picture="https://api.faviconkit.com/google.com/144"
|
||||||
|
/>
|
||||||
|
</BrowserRouter>
|
||||||
</TestCellContainer>,
|
</TestCellContainer>,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import CompanyChip from '@/companies/components/CompanyChip';
|
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||||
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/recoil-scope/hooks/useRecoilScopedState';
|
||||||
import { EditableCell } from '@/ui/components/editable-cell/EditableCell';
|
import { EditableCell } from '@/ui/components/editable-cell/EditableCell';
|
||||||
|
|||||||
@ -3,10 +3,10 @@ import { Link } from 'react-router-dom';
|
|||||||
import { Theme } from '@emotion/react';
|
import { Theme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import PersonPlaceholder from './person-placeholder.png';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
|
|
||||||
export type PersonChipPropsType = {
|
export type PersonChipPropsType = {
|
||||||
id?: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture?: string;
|
picture?: string;
|
||||||
};
|
};
|
||||||
@ -52,10 +52,12 @@ export function PersonChip({ id, name, picture }: PersonChipPropsType) {
|
|||||||
const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink;
|
const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink;
|
||||||
return (
|
return (
|
||||||
<ContainerComponent data-testid="person-chip" to={`/person/${id}`}>
|
<ContainerComponent data-testid="person-chip" to={`/person/${id}`}>
|
||||||
<img
|
<Avatar
|
||||||
data-testid="person-chip-image"
|
avatarUrl={picture}
|
||||||
src={picture ? picture.toString() : PersonPlaceholder.toString()}
|
colorId={id}
|
||||||
alt="person"
|
placeholder={name}
|
||||||
|
size={14}
|
||||||
|
type="rounded"
|
||||||
/>
|
/>
|
||||||
<StyledName>{name}</StyledName>
|
<StyledName>{name}</StyledName>
|
||||||
</ContainerComponent>
|
</ContainerComponent>
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
|
||||||
|
|
||||||
|
import { PersonChip } from '../PersonChip';
|
||||||
|
|
||||||
|
const meta: Meta<typeof PersonChip> = {
|
||||||
|
title: 'Modules/Companies/PersonChip',
|
||||||
|
component: PersonChip,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PersonChip>;
|
||||||
|
|
||||||
|
const TestCellContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ theme }) => theme.background.primary};
|
||||||
|
display: flex;
|
||||||
|
height: fit-content;
|
||||||
|
justify-content: space-between;
|
||||||
|
max-width: 250px;
|
||||||
|
min-width: 250px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SmallName: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<TestCellContainer>
|
||||||
|
<BrowserRouter>
|
||||||
|
<PersonChip id="tim_fake_id" name="Tim C." />
|
||||||
|
</BrowserRouter>
|
||||||
|
</TestCellContainer>,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BigName: Story = {
|
||||||
|
render: getRenderWrapperForComponent(
|
||||||
|
<TestCellContainer>
|
||||||
|
<BrowserRouter>
|
||||||
|
<PersonChip id="steve_fake_id" name="Steve J." />
|
||||||
|
</BrowserRouter>
|
||||||
|
</TestCellContainer>,
|
||||||
|
),
|
||||||
|
};
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
@ -71,6 +71,7 @@ export function MultipleEntitySelect<
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={entity.avatarUrl}
|
avatarUrl={entity.avatarUrl}
|
||||||
|
colorId={entity.id}
|
||||||
placeholder={entity.name}
|
placeholder={entity.name}
|
||||||
size={16}
|
size={16}
|
||||||
type={entity.avatarType ?? 'rounded'}
|
type={entity.avatarType ?? 'rounded'}
|
||||||
|
|||||||
@ -69,6 +69,7 @@ export function SingleEntitySelectBase<
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={entity.avatarUrl}
|
avatarUrl={entity.avatarUrl}
|
||||||
|
colorId={entity.id}
|
||||||
placeholder={entity.name}
|
placeholder={entity.name}
|
||||||
size={16}
|
size={16}
|
||||||
type={entity.avatarType ?? 'rounded'}
|
type={entity.avatarType ?? 'rounded'}
|
||||||
|
|||||||
@ -9,6 +9,13 @@ import {
|
|||||||
beautifyPastDateRelativeToNow,
|
beautifyPastDateRelativeToNow,
|
||||||
} from '@/utils/datetime/date-utils';
|
} from '@/utils/datetime/date-utils';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
id?: string;
|
||||||
|
logoOrAvatar?: string;
|
||||||
|
title: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
|
||||||
const StyledShowPageSummaryCard = styled.div`
|
const StyledShowPageSummaryCard = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -46,14 +53,11 @@ const StyledTooltip = styled(Tooltip)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export function ShowPageSummaryCard({
|
export function ShowPageSummaryCard({
|
||||||
|
id,
|
||||||
logoOrAvatar,
|
logoOrAvatar,
|
||||||
title,
|
title,
|
||||||
date,
|
date,
|
||||||
}: {
|
}: OwnProps) {
|
||||||
logoOrAvatar?: string;
|
|
||||||
title: string;
|
|
||||||
date: string;
|
|
||||||
}) {
|
|
||||||
const beautifiedCreatedAt =
|
const beautifiedCreatedAt =
|
||||||
date !== '' ? beautifyPastDateRelativeToNow(date) : '';
|
date !== '' ? beautifyPastDateRelativeToNow(date) : '';
|
||||||
const exactCreatedAt = date !== '' ? beautifyExactDate(date) : '';
|
const exactCreatedAt = date !== '' ? beautifyExactDate(date) : '';
|
||||||
@ -65,6 +69,7 @@ export function ShowPageSummaryCard({
|
|||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={logoOrAvatar}
|
avatarUrl={logoOrAvatar}
|
||||||
size={theme.icon.size.xl}
|
size={theme.icon.size.xl}
|
||||||
|
colorId={id}
|
||||||
placeholder={title}
|
placeholder={title}
|
||||||
/>
|
/>
|
||||||
<StyledInfoContainer>
|
<StyledInfoContainer>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { stringToHslColor } from '@/utils/string-to-hsl';
|
||||||
import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString';
|
import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString';
|
||||||
|
|
||||||
export type AvatarType = 'squared' | 'rounded';
|
export type AvatarType = 'squared' | 'rounded';
|
||||||
@ -8,26 +9,23 @@ type OwnProps = {
|
|||||||
avatarUrl: string | null | undefined;
|
avatarUrl: string | null | undefined;
|
||||||
size: number;
|
size: number;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
colorId?: string;
|
||||||
type?: AvatarType;
|
type?: AvatarType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StyledAvatar = styled.div<Omit<OwnProps, 'placeholder'>>`
|
export const StyledAvatar = styled.div<OwnProps & { colorId: string }>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: ${(props) =>
|
background-color: ${({ avatarUrl, colorId }) =>
|
||||||
!isNonEmptyString(props.avatarUrl)
|
!isNonEmptyString(avatarUrl) ? stringToHslColor(colorId, 75, 85) : 'none'};
|
||||||
? props.theme.background.tertiary
|
${({ avatarUrl }) =>
|
||||||
: 'none'};
|
isNonEmptyString(avatarUrl) ? `background-image: url(${avatarUrl});` : ''}
|
||||||
${(props) =>
|
|
||||||
isNonEmptyString(props.avatarUrl)
|
|
||||||
? `background-image: url(${props.avatarUrl});`
|
|
||||||
: ''}
|
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: ${(props) => (props.type === 'rounded' ? '50%' : '2px')};
|
border-radius: ${(props) => (props.type === 'rounded' ? '50%' : '2px')};
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ colorId }) => stringToHslColor(colorId, 75, 25)};
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
font-size: ${({ theme }) => theme.font.size.xs};
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
|
||||||
height: ${(props) => props.size}px;
|
height: ${(props) => props.size}px;
|
||||||
@ -39,12 +37,19 @@ export function Avatar({
|
|||||||
avatarUrl,
|
avatarUrl,
|
||||||
size,
|
size,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
colorId = placeholder,
|
||||||
type = 'squared',
|
type = 'squared',
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const noAvatarUrl = !isNonEmptyString(avatarUrl);
|
const noAvatarUrl = !isNonEmptyString(avatarUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledAvatar avatarUrl={avatarUrl} size={size} type={type}>
|
<StyledAvatar
|
||||||
|
avatarUrl={avatarUrl}
|
||||||
|
placeholder={placeholder}
|
||||||
|
size={size}
|
||||||
|
type={type}
|
||||||
|
colorId={colorId}
|
||||||
|
>
|
||||||
{noAvatarUrl && placeholder[0]?.toLocaleUpperCase()}
|
{noAvatarUrl && placeholder[0]?.toLocaleUpperCase()}
|
||||||
</StyledAvatar>
|
</StyledAvatar>
|
||||||
);
|
);
|
||||||
|
|||||||
13
front/src/modules/utils/string-to-hsl.ts
Normal file
13
front/src/modules/utils/string-to-hsl.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function stringToHslColor(
|
||||||
|
str: string,
|
||||||
|
saturation: number,
|
||||||
|
lightness: number,
|
||||||
|
) {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
const h = hash % 360;
|
||||||
|
return 'hsl(' + h + ', ' + saturation + '%, ' + lightness + '%)';
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ const EmailText = styled.span`
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
workspaceMember: {
|
workspaceMember: {
|
||||||
user: Pick<User, 'firstName' | 'lastName' | 'avatarUrl' | 'email'>;
|
user: Pick<User, 'id' | 'firstName' | 'lastName' | 'avatarUrl' | 'email'>;
|
||||||
};
|
};
|
||||||
accessory?: React.ReactNode;
|
accessory?: React.ReactNode;
|
||||||
};
|
};
|
||||||
@ -43,6 +43,7 @@ export function WorkspaceMemberCard({ workspaceMember, accessory }: OwnProps) {
|
|||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<Avatar
|
<Avatar
|
||||||
avatarUrl={getImageAbsoluteURIOrBase64(workspaceMember.user.avatarUrl)}
|
avatarUrl={getImageAbsoluteURIOrBase64(workspaceMember.user.avatarUrl)}
|
||||||
|
colorId={workspaceMember.user.id}
|
||||||
placeholder={workspaceMember.user.firstName || ''}
|
placeholder={workspaceMember.user.firstName || ''}
|
||||||
type="squared"
|
type="squared"
|
||||||
size={40}
|
size={40}
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export function CompanyShow() {
|
|||||||
<>
|
<>
|
||||||
<ShowPageLeftContainer>
|
<ShowPageLeftContainer>
|
||||||
<ShowPageSummaryCard
|
<ShowPageSummaryCard
|
||||||
|
id={company?.id}
|
||||||
logoOrAvatar={getLogoUrlFromDomainName(company?.domainName ?? '')}
|
logoOrAvatar={getLogoUrlFromDomainName(company?.domainName ?? '')}
|
||||||
title={company?.name ?? 'No name'}
|
title={company?.name ?? 'No name'}
|
||||||
date={company?.createdAt ?? ''}
|
date={company?.createdAt ?? ''}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export function PersonShow() {
|
|||||||
<>
|
<>
|
||||||
<ShowPageLeftContainer>
|
<ShowPageLeftContainer>
|
||||||
<ShowPageSummaryCard
|
<ShowPageSummaryCard
|
||||||
|
id={person?.id}
|
||||||
title={person?.displayName ?? 'No name'}
|
title={person?.displayName ?? 'No name'}
|
||||||
date={person?.createdAt ?? ''}
|
date={person?.createdAt ?? ''}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user