Add record chip for sender and add receivers (#3629)

* Add record chip for sender and add receivers

* Build enum for roles

* Rename var and use string literal

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
Thomas Trompette
2024-01-25 18:34:19 +01:00
committed by GitHub
parent b0c14ba5b9
commit 43b10cb00c
6 changed files with 109 additions and 46 deletions

View File

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import { EmailThreadMessageBody } from '@/activities/emails/components/EmailThreadMessageBody'; import { EmailThreadMessageBody } from '@/activities/emails/components/EmailThreadMessageBody';
import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/EmailThreadMessageBodyPreview'; import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/EmailThreadMessageBodyPreview';
import { EmailThreadMessageReceivers } from '@/activities/emails/components/EmailThreadMessageReceivers';
import { EmailThreadMessageSender } from '@/activities/emails/components/EmailThreadMessageSender'; import { EmailThreadMessageSender } from '@/activities/emails/components/EmailThreadMessageSender';
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant'; import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
@ -27,28 +28,6 @@ type EmailThreadMessageProps = {
participants: EmailThreadMessageParticipant[]; participants: EmailThreadMessageParticipant[];
}; };
const getDisplayNameFromParticipant = (
participant: EmailThreadMessageParticipant,
) => {
if (participant.person) {
return `${participant.person?.name?.firstName} ${participant.person?.name?.lastName}`;
}
if (participant.workspaceMember) {
return `${participant.workspaceMember?.name?.firstName} ${participant.workspaceMember?.name?.lastName}`;
}
if (participant.displayName) {
return participant.displayName;
}
if (participant.handle) {
return participant.handle;
}
return 'Unknown';
};
export const EmailThreadMessage = ({ export const EmailThreadMessage = ({
body, body,
sentAt, sentAt,
@ -57,25 +36,19 @@ export const EmailThreadMessage = ({
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const from = participants.find((participant) => participant.role === 'from'); const from = participants.find((participant) => participant.role === 'from');
const to = participants.filter((participant) => participant.role === 'to'); const receivers = participants.filter(
(participant) => participant.role !== 'from',
);
if (!from || to.length === 0) { if (!from || receivers.length === 0) {
return null; return null;
} }
const displayName = getDisplayNameFromParticipant(from);
const avatarUrl =
from.person?.avatarUrl ?? from.workspaceMember?.avatarUrl ?? '';
return ( return (
<StyledThreadMessage onClick={() => setIsOpen(!isOpen)}> <StyledThreadMessage onClick={() => setIsOpen(!isOpen)}>
<StyledThreadMessageHeader> <StyledThreadMessageHeader>
<EmailThreadMessageSender <EmailThreadMessageSender sender={from} sentAt={sentAt} />
displayName={displayName} {isOpen && <EmailThreadMessageReceivers receivers={receivers} />}
avatarUrl={avatarUrl}
sentAt={sentAt}
/>
</StyledThreadMessageHeader> </StyledThreadMessageHeader>
{isOpen ? ( {isOpen ? (
<EmailThreadMessageBody body={body} /> <EmailThreadMessageBody body={body} />

View File

@ -0,0 +1,33 @@
import styled from '@emotion/styled';
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
import { getDisplayNameFromParticipant } from '@/activities/emails/utils/getDisplayNameFromParticipant';
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
type EmailThreadMessageReceiversProps = {
receivers: EmailThreadMessageParticipant[];
};
const StyledThreadMessageReceivers = styled.span`
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
padding: ${({ theme }) => theme.spacing(2, 0, 0, 1)};
width: 50%;
`;
export const EmailThreadMessageReceivers = ({
receivers,
}: EmailThreadMessageReceiversProps) => {
const displayedReceivers = receivers
.map((receiver) => getDisplayNameFromParticipant({ participant: receiver }))
.join(', ');
const body = `to: ${displayedReceivers}`;
return (
<StyledThreadMessageReceivers>
<OverflowingTextWithTooltip text={body} />
</StyledThreadMessageReceivers>
);
};

View File

@ -1,6 +1,10 @@
import React from 'react'; import React from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
import { getDisplayNameFromParticipant } from '@/activities/emails/utils/getDisplayNameFromParticipant';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordChip } from '@/object-record/components/RecordChip';
import { Avatar } from '@/users/components/Avatar'; import { Avatar } from '@/users/components/Avatar';
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
@ -32,26 +36,42 @@ const StyledThreadMessageSentAt = styled.div`
`; `;
type EmailThreadMessageSenderProps = { type EmailThreadMessageSenderProps = {
displayName: string; sender: EmailThreadMessageParticipant;
avatarUrl: string;
sentAt: string; sentAt: string;
}; };
export const EmailThreadMessageSender = ({ export const EmailThreadMessageSender = ({
displayName, sender,
avatarUrl,
sentAt, sentAt,
}: EmailThreadMessageSenderProps) => { }: EmailThreadMessageSenderProps) => {
const { person, workspaceMember } = sender;
const displayName = getDisplayNameFromParticipant({
participant: sender,
shouldUseFullName: true,
});
const avatarUrl = person?.avatarUrl ?? workspaceMember?.avatarUrl ?? '';
return ( return (
<StyledEmailThreadMessageSender> <StyledEmailThreadMessageSender>
<StyledEmailThreadMessageSenderUser> <StyledEmailThreadMessageSenderUser>
<StyledAvatar {person ? (
avatarUrl={avatarUrl} <RecordChip
type="rounded" objectNameSingular={CoreObjectNameSingular.Person}
placeholder={displayName} record={person}
size="sm" />
/> ) : (
<StyledSenderName>{displayName}</StyledSenderName> <>
<StyledAvatar
avatarUrl={avatarUrl}
type="rounded"
placeholder={displayName}
size="sm"
/>
<StyledSenderName>{displayName}</StyledSenderName>
</>
)}
</StyledEmailThreadMessageSenderUser> </StyledEmailThreadMessageSenderUser>
<StyledThreadMessageSentAt> <StyledThreadMessageSentAt>
{beautifyPastDateRelativeToNow(sentAt)} {beautifyPastDateRelativeToNow(sentAt)}

View File

@ -0,0 +1 @@
export type EmailParticipantRole = 'from' | 'to' | 'cc' | 'bcc';

View File

@ -1,10 +1,11 @@
import { EmailParticipantRole } from '@/activities/emails/types/EmailParticipantRole';
import { Person } from '@/people/types/Person'; import { Person } from '@/people/types/Person';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
export type EmailThreadMessageParticipant = { export type EmailThreadMessageParticipant = {
displayName: string; displayName: string;
handle: string; handle: string;
role: string; role: EmailParticipantRole;
person: Person; person: Person;
workspaceMember: WorkspaceMember; workspaceMember: WorkspaceMember;
}; };

View File

@ -0,0 +1,35 @@
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
export const getDisplayNameFromParticipant = ({
participant,
shouldUseFullName = false,
}: {
participant: EmailThreadMessageParticipant;
shouldUseFullName?: boolean;
}) => {
if (participant.person) {
return (
`${participant.person?.name?.firstName}` +
(shouldUseFullName ? ` ${participant.person?.name?.lastName}` : '')
);
}
if (participant.workspaceMember) {
return (
participant.workspaceMember?.name?.firstName +
(shouldUseFullName
? ` ${participant.workspaceMember?.name?.lastName}`
: '')
);
}
if (participant.displayName) {
return participant.displayName;
}
if (participant.handle) {
return participant.handle;
}
return 'Unknown';
};