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 { IconMail } from '@/ui/display/icon';
|
||||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||||
interface ThreadHeaderProps {
|
|
||||||
|
type EmailThreadHeaderProps = {
|
||||||
subject: string;
|
subject: string;
|
||||||
lastMessageSentAt: Date;
|
lastMessageSentAt: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -36,10 +37,10 @@ const StyledContent = styled.span`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ThreadHeader = ({
|
export const EmailThreadHeader = ({
|
||||||
subject,
|
subject,
|
||||||
lastMessageSentAt,
|
lastMessageSentAt,
|
||||||
}: ThreadHeaderProps) => {
|
}: EmailThreadHeaderProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<Tag Icon={IconMail} color="gray" text="Email" onClick={() => {}} />
|
<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 styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useOpenThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenThreadRightDrawer';
|
|
||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { TimelineThread } from '~/generated/graphql';
|
import { TimelineThread } from '~/generated/graphql';
|
||||||
@ -78,19 +77,19 @@ const StyledReceivedAt = styled.div`
|
|||||||
padding: ${({ theme }) => theme.spacing(0, 1)};
|
padding: ${({ theme }) => theme.spacing(0, 1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type ThreadPreviewProps = {
|
type EmailThreadPreviewProps = {
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
thread: TimelineThread;
|
thread: TimelineThread;
|
||||||
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ThreadPreview = ({ divider, thread }: ThreadPreviewProps) => {
|
export const EmailThreadPreview = ({
|
||||||
const openMessageThreadRightDrawer = useOpenThreadRightDrawer();
|
divider,
|
||||||
|
thread,
|
||||||
|
onClick,
|
||||||
|
}: EmailThreadPreviewProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledCardContent
|
<StyledCardContent onClick={() => onClick()} divider={divider}>
|
||||||
onClick={() => openMessageThreadRightDrawer()}
|
|
||||||
divider={divider}
|
|
||||||
>
|
|
||||||
<StyledHeading unread={!thread.read}>
|
<StyledHeading unread={!thread.read}>
|
||||||
<StyledAvatar
|
<StyledAvatar
|
||||||
avatarUrl={thread.senderPictureUrl}
|
avatarUrl={thread.senderPictureUrl}
|
||||||
@ -1,7 +1,12 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
import styled from '@emotion/styled';
|
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 { getTimelineThreadsFromCompanyId } from '@/activities/emails/queries/getTimelineThreadsFromCompanyId';
|
||||||
import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId';
|
import { getTimelineThreadsFromPersonId } from '@/activities/emails/queries/getTimelineThreadsFromPersonId';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
@ -12,7 +17,6 @@ import {
|
|||||||
} from '@/ui/display/typography/components/H1Title';
|
} from '@/ui/display/typography/components/H1Title';
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
import { Card } from '@/ui/layout/card/components/Card';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
import { TimelineThread } from '~/generated/graphql';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -30,7 +34,13 @@ const StyledEmailCount = styled.span`
|
|||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
|
export const EmailThreads = ({
|
||||||
|
entity,
|
||||||
|
}: {
|
||||||
|
entity: ActivityTargetableObject;
|
||||||
|
}) => {
|
||||||
|
const { openEmailThread } = useEmailThread();
|
||||||
|
|
||||||
const threadQuery =
|
const threadQuery =
|
||||||
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||||
? getTimelineThreadsFromPersonId
|
? getTimelineThreadsFromPersonId
|
||||||
@ -49,12 +59,16 @@ export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timelineThreads: TimelineThread[] =
|
// To use once the id is returned by the query
|
||||||
threads.data[
|
|
||||||
entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
// const fetchedTimelineThreads: TimelineThread[] =
|
||||||
? 'getTimelineThreadsFromPersonId'
|
// threads.data[
|
||||||
: 'getTimelineThreadsFromCompanyId'
|
// entity.targetObjectNameSingular === CoreObjectNameSingular.Person
|
||||||
];
|
// ? 'getTimelineThreadsFromPersonId'
|
||||||
|
// : 'getTimelineThreadsFromCompanyId'
|
||||||
|
// ];
|
||||||
|
|
||||||
|
const timelineThreads = mockedEmailThreads;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -72,11 +86,12 @@ export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
|
|||||||
/>
|
/>
|
||||||
<Card>
|
<Card>
|
||||||
{timelineThreads &&
|
{timelineThreads &&
|
||||||
timelineThreads.map((thread: TimelineThread, index: number) => (
|
timelineThreads.map((thread: MockedThread, index: number) => (
|
||||||
<ThreadPreview
|
<EmailThreadPreview
|
||||||
key={index}
|
key={index}
|
||||||
divider={index < timelineThreads.length - 1}
|
divider={index < timelineThreads.length - 1}
|
||||||
thread={thread}
|
thread={thread}
|
||||||
|
onClick={() => openEmailThread(thread)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { MockedThread } from '@/activities/emails/mocks/mockedEmailThreads';
|
||||||
|
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
|
||||||
|
import { viewableEmailThreadState } from '@/activities/emails/state/viewableEmailThreadState';
|
||||||
|
|
||||||
|
export const useEmailThread = () => {
|
||||||
|
const [, setViewableEmailThread] = useRecoilState(viewableEmailThreadState);
|
||||||
|
|
||||||
|
const openEmailThredRightDrawer = useOpenEmailThreadRightDrawer();
|
||||||
|
|
||||||
|
const openEmailThread = (thread: MockedThread) => {
|
||||||
|
openEmailThredRightDrawer();
|
||||||
|
|
||||||
|
setViewableEmailThread(thread);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openEmailThread,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { Scalars, TimelineThread } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export type MockedThread = {
|
||||||
|
id: string;
|
||||||
|
} & TimelineThread;
|
||||||
|
|
||||||
|
export type MockedEmailUser = {
|
||||||
|
avatarUrl: string;
|
||||||
|
displayName: string;
|
||||||
|
workspaceMemberId?: string;
|
||||||
|
personId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MockedMessage = {
|
||||||
|
id: string;
|
||||||
|
from: MockedEmailUser;
|
||||||
|
to: MockedEmailUser[];
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
sentAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedEmailThreads: MockedThread[] = [
|
||||||
|
{
|
||||||
|
__typename: 'TimelineThread',
|
||||||
|
id: '1',
|
||||||
|
body: 'This is a test email' as Scalars['String'],
|
||||||
|
numberOfMessagesInThread: 5 as Scalars['Float'],
|
||||||
|
read: true as Scalars['Boolean'],
|
||||||
|
receivedAt: new Date().toISOString() as Scalars['DateTime'],
|
||||||
|
senderName: 'Thom Trp' as Scalars['String'],
|
||||||
|
senderPictureUrl: '' as Scalars['String'],
|
||||||
|
subject: 'Test email' as Scalars['String'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: 'TimelineThread',
|
||||||
|
id: '2',
|
||||||
|
body: 'This is a second test email' as Scalars['String'],
|
||||||
|
numberOfMessagesInThread: 5 as Scalars['Float'],
|
||||||
|
read: true as Scalars['Boolean'],
|
||||||
|
receivedAt: new Date().toISOString() as Scalars['DateTime'],
|
||||||
|
senderName: 'Coco Den' as Scalars['String'],
|
||||||
|
senderPictureUrl: '' as Scalars['String'],
|
||||||
|
subject: 'Test email number 2' as Scalars['String'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mockedMessagesByThread: Map<string, MockedMessage[]> = new Map([
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
Array.from({ length: 5 }).map((_, i) => ({
|
||||||
|
id: `id${i + 1}`,
|
||||||
|
from: {
|
||||||
|
avatarUrl: '',
|
||||||
|
displayName: `User ${i + 1}`,
|
||||||
|
workspaceMemberId: `workspaceMemberId${i + 1}`,
|
||||||
|
personId: `personId${i + 1}`,
|
||||||
|
},
|
||||||
|
to: [
|
||||||
|
{
|
||||||
|
avatarUrl: 'https://favicon.twenty.com/qonto.com',
|
||||||
|
displayName: `User ${i + 2}`,
|
||||||
|
workspaceMemberId: `workspaceMemberId${i + 1}`,
|
||||||
|
personId: `personId${i + 2}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subject: `Subject ${i + 1}`,
|
||||||
|
body: `Body ${
|
||||||
|
i + 1
|
||||||
|
}. I am testing a very long body. I am adding more text.
|
||||||
|
I also want to test a new line. To see if it works.
|
||||||
|
|
||||||
|
I am adding a new paragraph.
|
||||||
|
|
||||||
|
Thomas`,
|
||||||
|
sentAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '',
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'2',
|
||||||
|
Array.from({ length: 5 }).map((_, i) => ({
|
||||||
|
id: `id${i + 10}`,
|
||||||
|
from: {
|
||||||
|
avatarUrl: '',
|
||||||
|
displayName: `Other user ${i + 1}`,
|
||||||
|
workspaceMemberId: `workspaceMemberId${i + 1}`,
|
||||||
|
personId: `personId${i + 1}`,
|
||||||
|
},
|
||||||
|
to: [
|
||||||
|
{
|
||||||
|
avatarUrl: 'https://favicon.twenty.com/qonto.com',
|
||||||
|
displayName: `Other user ${i + 2}`,
|
||||||
|
workspaceMemberId: `workspaceMemberId${i + 1}`,
|
||||||
|
personId: `personId${i + 2}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subject: `Subject ${i + 1}`,
|
||||||
|
body: `Body ${
|
||||||
|
i + 1
|
||||||
|
}. Hello, I am testing a very long body. I am adding more text.
|
||||||
|
|
||||||
|
I am adding a new paragraph.
|
||||||
|
|
||||||
|
Thomas`,
|
||||||
|
sentAt: DateTime.fromFormat('2021-03-12', 'yyyy-MM-dd').toISO() ?? '',
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
]);
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader';
|
||||||
|
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
|
||||||
|
import { mockedMessagesByThread } from '@/activities/emails/mocks/mockedEmailThreads';
|
||||||
|
import { viewableEmailThreadState } from '@/activities/emails/state/viewableEmailThreadState';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RightDrawerEmailThread = () => {
|
||||||
|
const viewableEmailThread = useRecoilValue(viewableEmailThreadState);
|
||||||
|
|
||||||
|
if (!viewableEmailThread) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockedMessages =
|
||||||
|
mockedMessagesByThread.get(viewableEmailThread.id) ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<EmailThreadHeader
|
||||||
|
subject={viewableEmailThread.subject}
|
||||||
|
lastMessageSentAt={viewableEmailThread.receivedAt}
|
||||||
|
/>
|
||||||
|
{mockedMessages.map((message) => (
|
||||||
|
<EmailThreadMessage
|
||||||
|
key={message.id}
|
||||||
|
id={message.id}
|
||||||
|
from={message.from}
|
||||||
|
to={message.to}
|
||||||
|
body={message.body}
|
||||||
|
sentAt={message.sentAt}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -10,7 +10,7 @@ const StyledTopBarWrapper = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RightDrawerThreadTopBar = () => {
|
export const RightDrawerEmailThreadTopBar = () => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { ThreadHeader } from '@/activities/emails/components/ThreadHeader';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
overflow-y: auto;
|
|
||||||
position: relative;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RightDrawerThread = () => {
|
|
||||||
const mockedThread = {
|
|
||||||
subject: 'Tes with long subject, very long subject, very long subject',
|
|
||||||
receivedAt: new Date(),
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<StyledContainer>
|
|
||||||
<ThreadHeader
|
|
||||||
subject={mockedThread.subject}
|
|
||||||
lastMessageSentAt={mockedThread.receivedAt}
|
|
||||||
/>
|
|
||||||
</StyledContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { RightDrawerThreadTopBar } from '@/activities/emails/right-drawer/components/RightDrawerThreadTopBar';
|
import { RightDrawerEmailThreadTopBar } from '@/activities/emails/right-drawer/components/RightDrawerEmailThreadTopBar';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
const meta: Meta<typeof RightDrawerThreadTopBar> = {
|
const meta: Meta<typeof RightDrawerEmailThreadTopBar> = {
|
||||||
title: 'Modules/Activities/Emails/RightDrawer/RightDrawerThreadTopBar',
|
title: 'Modules/Activities/Emails/RightDrawer/RightDrawerEmailThreadTopBar',
|
||||||
component: RightDrawerThreadTopBar,
|
component: RightDrawerEmailThreadTopBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<div style={{ width: '500px' }}>
|
<div style={{ width: '500px' }}>
|
||||||
@ -21,6 +21,6 @@ const meta: Meta<typeof RightDrawerThreadTopBar> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof RightDrawerThreadTopBar>;
|
type Story = StoryObj<typeof RightDrawerEmailThreadTopBar>;
|
||||||
|
|
||||||
export const Default: Story = {};
|
export const Default: Story = {};
|
||||||
@ -3,12 +3,12 @@ import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDraw
|
|||||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
|
||||||
export const useOpenThreadRightDrawer = () => {
|
export const useOpenEmailThreadRightDrawer = () => {
|
||||||
const { openRightDrawer } = useRightDrawer();
|
const { openRightDrawer } = useRightDrawer();
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
openRightDrawer(RightDrawerPages.ViewThread);
|
openRightDrawer(RightDrawerPages.ViewEmailThread);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
import { MockedThread } from '@/activities/emails/mocks/mockedEmailThreads';
|
||||||
|
|
||||||
|
export const viewableEmailThreadState = atom<MockedThread | null>({
|
||||||
|
key: 'viewableEmailThreadState',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { RightDrawerThread } from '@/activities/emails/right-drawer/components/RightDrawerThread';
|
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
|
||||||
import { RightDrawerThreadTopBar } from '@/activities/emails/right-drawer/components/RightDrawerThreadTopBar';
|
import { RightDrawerEmailThreadTopBar } from '@/activities/emails/right-drawer/components/RightDrawerEmailThreadTopBar';
|
||||||
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
||||||
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ export const RightDrawerRouter = () => {
|
|||||||
page = <RightDrawerEditActivity />;
|
page = <RightDrawerEditActivity />;
|
||||||
topBar = <RightDrawerActivityTopBar />;
|
topBar = <RightDrawerActivityTopBar />;
|
||||||
break;
|
break;
|
||||||
case RightDrawerPages.ViewThread:
|
case RightDrawerPages.ViewEmailThread:
|
||||||
page = <RightDrawerThread />;
|
page = <RightDrawerEmailThread />;
|
||||||
topBar = <RightDrawerThreadTopBar />;
|
topBar = <RightDrawerEmailThreadTopBar />;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export enum RightDrawerPages {
|
export enum RightDrawerPages {
|
||||||
CreateActivity = 'create-activity',
|
CreateActivity = 'create-activity',
|
||||||
EditActivity = 'edit-activity',
|
EditActivity = 'edit-activity',
|
||||||
ViewThread = 'view-thread',
|
ViewEmailThread = 'view-email-thread',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { Threads } from '@/activities/emails/components/Threads';
|
import { EmailThreads } from '@/activities/emails/components/EmailThreads';
|
||||||
import { Attachments } from '@/activities/files/components/Attachments';
|
import { Attachments } from '@/activities/files/components/Attachments';
|
||||||
import { Notes } from '@/activities/notes/components/Notes';
|
import { Notes } from '@/activities/notes/components/Notes';
|
||||||
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
|
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
|
||||||
@ -115,7 +115,7 @@ export const ShowPageRightContainer = ({
|
|||||||
{activeTabId === 'files' && (
|
{activeTabId === 'files' && (
|
||||||
<Attachments targetableObject={targetableObject} />
|
<Attachments targetableObject={targetableObject} />
|
||||||
)}
|
)}
|
||||||
{activeTabId === 'emails' && <Threads entity={targetableObject} />}
|
{activeTabId === 'emails' && <EmailThreads entity={targetableObject} />}
|
||||||
</StyledShowPageRightContainer>
|
</StyledShowPageRightContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -212,7 +212,7 @@ export const mockedActivities: Array<MockedActivity> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const mockedThreads: TimelineThread[] = [
|
export const mockedEmailThreads: TimelineThread[] = [
|
||||||
{
|
{
|
||||||
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dignissim nisi eu tellus dapibus, egestas placerat risus placerat. Praesent eget arcu consectetur, efficitur felis.',
|
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras dignissim nisi eu tellus dapibus, egestas placerat risus placerat. Praesent eget arcu consectetur, efficitur felis.',
|
||||||
numberOfMessagesInThread: 4,
|
numberOfMessagesInThread: 4,
|
||||||
|
|||||||
Reference in New Issue
Block a user