Build message threads (#3593)
* Adding message thread component * Add state and mocks * Rename components and use local state for messages --------- Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
@ -3,10 +3,11 @@ import styled from '@emotion/styled';
|
||||
import { IconMail } from '@/ui/display/icon';
|
||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||
interface ThreadHeaderProps {
|
||||
|
||||
type EmailThreadHeaderProps = {
|
||||
subject: string;
|
||||
lastMessageSentAt: Date;
|
||||
}
|
||||
lastMessageSentAt: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: flex-start;
|
||||
@ -36,10 +37,10 @@ const StyledContent = styled.span`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const ThreadHeader = ({
|
||||
export const EmailThreadHeader = ({
|
||||
subject,
|
||||
lastMessageSentAt,
|
||||
}: ThreadHeaderProps) => {
|
||||
}: EmailThreadHeaderProps) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Tag Icon={IconMail} color="gray" text="Email" onClick={() => {}} />
|
||||
@ -0,0 +1,56 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { EmailThreadMessageBody } from '@/activities/emails/components/EmailThreadMessageBody';
|
||||
import { EmailThreadMessageBodyPreview } from '@/activities/emails/components/EmailThreadMessageBodyPreview';
|
||||
import { EmailThreadMessageSender } from '@/activities/emails/components/EmailThreadMessageSender';
|
||||
import { MockedEmailUser } from '@/activities/emails/mocks/mockedEmailThreads';
|
||||
|
||||
const StyledThreadMessage = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(4, 6)};
|
||||
`;
|
||||
|
||||
const StyledThreadMessageHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type EmailThreadMessageProps = {
|
||||
id: string;
|
||||
body: string;
|
||||
sentAt: string;
|
||||
from: MockedEmailUser;
|
||||
to: MockedEmailUser[];
|
||||
};
|
||||
|
||||
export const EmailThreadMessage = ({
|
||||
body,
|
||||
sentAt,
|
||||
from,
|
||||
}: EmailThreadMessageProps) => {
|
||||
const { displayName, avatarUrl } = from;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<StyledThreadMessage onClick={() => setIsOpen(!isOpen)}>
|
||||
<StyledThreadMessageHeader>
|
||||
<EmailThreadMessageSender
|
||||
displayName={displayName}
|
||||
avatarUrl={avatarUrl}
|
||||
sentAt={sentAt}
|
||||
/>
|
||||
</StyledThreadMessageHeader>
|
||||
{isOpen ? (
|
||||
<EmailThreadMessageBody body={body} />
|
||||
) : (
|
||||
<EmailThreadMessageBodyPreview body={body} />
|
||||
)}
|
||||
</StyledThreadMessage>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledThreadMessageBody = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
white-space: pre-line;
|
||||
`;
|
||||
|
||||
type EmailThreadMessageBodyProps = {
|
||||
body: string;
|
||||
};
|
||||
|
||||
export const EmailThreadMessageBody = ({
|
||||
body,
|
||||
}: EmailThreadMessageBodyProps) => {
|
||||
return <StyledThreadMessageBody>{body}</StyledThreadMessageBody>;
|
||||
};
|
||||
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledThreadMessageBodyPreview = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
`;
|
||||
|
||||
type EmailThreadMessageBodyPreviewProps = {
|
||||
body: string;
|
||||
};
|
||||
|
||||
export const EmailThreadMessageBodyPreview = ({
|
||||
body,
|
||||
}: EmailThreadMessageBodyPreviewProps) => {
|
||||
return (
|
||||
<StyledThreadMessageBodyPreview>{body}</StyledThreadMessageBodyPreview>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||
|
||||
const StyledEmailThreadMessageSender = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const StyledEmailThreadMessageSenderUser = styled.div`
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledAvatar = styled(Avatar)`
|
||||
margin: ${({ theme }) => theme.spacing(0, 1)};
|
||||
`;
|
||||
|
||||
const StyledSenderName = styled.span`
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const StyledThreadMessageSentAt = styled.div`
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
`;
|
||||
|
||||
type EmailThreadMessageSenderProps = {
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
sentAt: string;
|
||||
};
|
||||
|
||||
export const EmailThreadMessageSender = ({
|
||||
displayName,
|
||||
avatarUrl,
|
||||
sentAt,
|
||||
}: EmailThreadMessageSenderProps) => {
|
||||
return (
|
||||
<StyledEmailThreadMessageSender>
|
||||
<StyledEmailThreadMessageSenderUser>
|
||||
<StyledAvatar
|
||||
avatarUrl={avatarUrl}
|
||||
type="rounded"
|
||||
placeholder={displayName}
|
||||
size="sm"
|
||||
/>
|
||||
<StyledSenderName>{displayName}</StyledSenderName>
|
||||
</StyledEmailThreadMessageSenderUser>
|
||||
<StyledThreadMessageSentAt>
|
||||
{beautifyPastDateRelativeToNow(sentAt)}
|
||||
</StyledThreadMessageSentAt>
|
||||
</StyledEmailThreadMessageSender>
|
||||
);
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useOpenThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenThreadRightDrawer';
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { TimelineThread } from '~/generated/graphql';
|
||||
@ -78,19 +77,19 @@ const StyledReceivedAt = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(0, 1)};
|
||||
`;
|
||||
|
||||
type ThreadPreviewProps = {
|
||||
type EmailThreadPreviewProps = {
|
||||
divider?: boolean;
|
||||
thread: TimelineThread;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const ThreadPreview = ({ divider, thread }: ThreadPreviewProps) => {
|
||||
const openMessageThreadRightDrawer = useOpenThreadRightDrawer();
|
||||
|
||||
export const EmailThreadPreview = ({
|
||||
divider,
|
||||
thread,
|
||||
onClick,
|
||||
}: EmailThreadPreviewProps) => {
|
||||
return (
|
||||
<StyledCardContent
|
||||
onClick={() => openMessageThreadRightDrawer()}
|
||||
divider={divider}
|
||||
>
|
||||
<StyledCardContent onClick={() => onClick()} divider={divider}>
|
||||
<StyledHeading unread={!thread.read}>
|
||||
<StyledAvatar
|
||||
avatarUrl={thread.senderPictureUrl}
|
||||
@ -1,7 +1,12 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ThreadPreview } from '@/activities/emails/components/ThreadPreview';
|
||||
import { EmailThreadPreview } from '@/activities/emails/components/EmailThreadPreview';
|
||||
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
||||
import {
|
||||
mockedEmailThreads,
|
||||
MockedThread,
|
||||
} from '@/activities/emails/mocks/mockedEmailThreads';
|
||||
import { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
|
||||
import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
@ -12,7 +17,6 @@ import {
|
||||
} from '@/ui/display/typography/components/H1Title';
|
||||
import { Card } from '@/ui/layout/card/components/Card';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { TimelineThread } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -30,7 +34,13 @@ const StyledEmailCount = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
`;
|
||||
|
||||
export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
|
||||
export const EmailThreads = ({
|
||||
entity,
|
||||
}: {
|
||||
entity: ActivityTargetableObject;
|
||||
}) => {
|
||||
const { openEmailThread } = useEmailThread();
|
||||
|
||||
const threadQuery =
|
||||
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||
? getTimelineThreadsFromPersonId
|
||||
@ -49,12 +59,16 @@ export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const timelineThreads: TimelineThread[] =
|
||||
threads.data[
|
||||
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||
? 'getTimelineThreadsFromPersonId'
|
||||
: 'getTimelineThreadsFromCompanyId'
|
||||
];
|
||||
// To use once the id is returned by the query
|
||||
|
||||
// const fetchedTimelineThreads: TimelineThread[] =
|
||||
// threads.data[
|
||||
// entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||
// ? 'getTimelineThreadsFromPersonId'
|
||||
// : 'getTimelineThreadsFromCompanyId'
|
||||
// ];
|
||||
|
||||
const timelineThreads = mockedEmailThreads;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
@ -72,11 +86,12 @@ export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
|
||||
/>
|
||||
<Card>
|
||||
{timelineThreads &&
|
||||
timelineThreads.map((thread: TimelineThread, index: number) => (
|
||||
<ThreadPreview
|
||||
timelineThreads.map((thread: MockedThread, index: number) => (
|
||||
<EmailThreadPreview
|
||||
key={index}
|
||||
divider={index < timelineThreads.length - 1}
|
||||
thread={thread}
|
||||
onClick={() => openEmailThread(thread)}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
Reference in New Issue
Block a user