From 5deb0abe4d2e964d449440eaacc8585ba56ebede Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:17:55 +0530 Subject: [PATCH] added "reply in gmail" button (#6754) Issue https://github.com/twentyhq/twenty/issues/4217 --- .../components/RightDrawerEmailThread.tsx | 62 +++++++++++--- .../useRightDrawerEmailThread.test.tsx | 2 +- .../hooks/useRightDrawerEmailThread.ts | 80 +++++++++++++++++-- .../types/MessageChannelMessageAssociation.ts | 7 ++ .../types/CoreObjectNameSingular.ts | 1 + 5 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/emails/types/MessageChannelMessageAssociation.ts diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx index a03223780..bd4003ce3 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/components/RightDrawerEmailThread.tsx @@ -9,25 +9,46 @@ import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMe import { IntermediaryMessages } from '@/activities/emails/right-drawer/components/IntermediaryMessages'; import { useRightDrawerEmailThread } from '@/activities/emails/right-drawer/hooks/useRightDrawerEmailThread'; import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState'; +import { Button } from '@/ui/input/button/components/Button'; import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener'; import { messageThreadState } from '@/ui/layout/right-drawer/states/messageThreadState'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; +import { IconArrowBackUp } from 'twenty-ui'; const StyledContainer = styled.div` box-sizing: border-box; display: flex; flex-direction: column; - height: 100%; + height: 85%; justify-content: flex-start; overflow-y: auto; position: relative; `; +const StyledButtonContainer = styled.div` + background: ${({ theme }) => theme.background.secondary}; + bottom: 0; + display: flex; + height: 110px; + left: 0; + padding-left: ${({ theme }) => theme.spacing(7)}; + padding-top: ${({ theme }) => theme.spacing(5)}; + position: fixed; + right: 0; +`; + export const RightDrawerEmailThread = () => { const setMessageThread = useSetRecoilState(messageThreadState); - const { thread, messages, fetchMoreMessages, loading } = - useRightDrawerEmailThread(); + const { + thread, + messages, + fetchMoreMessages, + threadLoading, + messageThreadExternalId, + connectedAccountHandle, + messageChannelLoading, + } = useRightDrawerEmailThread(); const visibleMessages = useMemo(() => { return messages.filter(({ messageParticipants }) => { @@ -67,10 +88,6 @@ export const RightDrawerEmailThread = () => { ), }); - if (!thread) { - return null; - } - const visibleMessagesCount = visibleMessages.length; const is5OrMoreMessages = visibleMessagesCount >= 5; const firstMessages = visibleMessages.slice( @@ -83,9 +100,26 @@ export const RightDrawerEmailThread = () => { const lastMessage = visibleMessages[visibleMessagesCount - 1]; const subject = visibleMessages[0]?.subject; + const canReply = useMemo(() => { + return ( + connectedAccountHandle && lastMessage && messageThreadExternalId != null + ); + }, [connectedAccountHandle, lastMessage, messageThreadExternalId]); + + const handleReplyClick = () => { + if (!canReply) { + return; + } + + const url = `https://mail.google.com/mail/?authuser=${connectedAccountHandle}#all/${messageThreadExternalId}`; + window.open(url, '_blank'); + }; + if (!thread) { + return null; + } return ( - {loading ? ( + {threadLoading ? ( ) : ( <> @@ -110,11 +144,21 @@ export const RightDrawerEmailThread = () => { isExpanded /> )} + {canReply && !messageChannelLoading ? ( + + + + ) : null} ); }; diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx index 1bda68947..f88c7b79a 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx @@ -50,7 +50,7 @@ describe('useRightDrawerEmailThread', () => { expect(result.current.thread).toBeDefined(); expect(result.current.messages).toEqual(mockMessages); - expect(result.current.loading).toBeFalsy(); + expect(result.current.threadLoading).toBeFalsy(); expect(result.current.fetchMoreMessages).toBeInstanceOf(Function); }); }); diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts index da57c1c4d..23d004f05 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/useRightDrawerEmailThread.ts @@ -1,9 +1,12 @@ -import { useCallback } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { fetchAllThreadMessagesOperationSignatureFactory } from '@/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory'; import { EmailThread } from '@/activities/emails/types/EmailThread'; import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage'; + +import { MessageChannel } from '@/accounts/types/MessageChannel'; +import { MessageChannelMessageAssociation } from '@/activities/emails/types/MessageChannelMessageAssociation'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; @@ -14,6 +17,11 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; export const useRightDrawerEmailThread = () => { const viewableRecordId = useRecoilValue(viewableRecordIdState); const { upsertRecords } = useUpsertRecordsInStore(); + const [lastMessageId, setLastMessageId] = useState(null); + const [lastMessageChannelId, setLastMessageChannelId] = useState< + string | null + >(null); + const [isMessagesFetchComplete, setIsMessagesFetchComplete] = useState(false); const { record: thread } = useFindOneRecord({ objectNameSingular: CoreObjectNameSingular.MessageThread, @@ -38,8 +46,9 @@ export const useRightDrawerEmailThread = () => { const { records: messages, - loading, + loading: messagesLoading, fetchMoreRecords, + hasNextPage, } = useFindManyRecords({ limit: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.limit, filter: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.filter, @@ -51,15 +60,76 @@ export const useRightDrawerEmailThread = () => { }); const fetchMoreMessages = useCallback(() => { - if (!loading) { + if (!messagesLoading && hasNextPage) { fetchMoreRecords(); + } else if (!hasNextPage) { + setIsMessagesFetchComplete(true); } - }, [fetchMoreRecords, loading]); + }, [fetchMoreRecords, messagesLoading, hasNextPage]); + + useEffect(() => { + if (messages.length > 0 && isMessagesFetchComplete) { + const lastMessage = messages[messages.length - 1]; + setLastMessageId(lastMessage.id); + } + }, [messages, isMessagesFetchComplete]); + + const { records: messageChannelMessageAssociationData } = + useFindManyRecords({ + filter: { + messageId: { + eq: lastMessageId ?? '', + }, + }, + objectNameSingular: + CoreObjectNameSingular.MessageChannelMessageAssociation, + recordGqlFields: { + id: true, + messageId: true, + messageChannelId: true, + messageThreadExternalId: true, + }, + skip: !lastMessageId || !isMessagesFetchComplete, + }); + + useEffect(() => { + if (messageChannelMessageAssociationData.length > 0) { + setLastMessageChannelId( + messageChannelMessageAssociationData[0].messageChannelId, + ); + } + }, [messageChannelMessageAssociationData]); + + const { records: messageChannelData, loading: messageChannelLoading } = + useFindManyRecords({ + filter: { + id: { + eq: lastMessageChannelId ?? '', + }, + }, + objectNameSingular: CoreObjectNameSingular.MessageChannel, + recordGqlFields: { + id: true, + handle: true, + connectedAccountId: true, + }, + skip: !lastMessageChannelId, + }); + + const messageThreadExternalId = + messageChannelMessageAssociationData.length > 0 + ? messageChannelMessageAssociationData[0].messageThreadExternalId + : null; + const connectedAccountHandle = + messageChannelData.length > 0 ? messageChannelData[0].handle : null; return { thread, messages, - loading, + messageThreadExternalId, + connectedAccountHandle, + threadLoading: messagesLoading, + messageChannelLoading, fetchMoreMessages, }; }; diff --git a/packages/twenty-front/src/modules/activities/emails/types/MessageChannelMessageAssociation.ts b/packages/twenty-front/src/modules/activities/emails/types/MessageChannelMessageAssociation.ts new file mode 100644 index 000000000..ef0bfb1b6 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/emails/types/MessageChannelMessageAssociation.ts @@ -0,0 +1,7 @@ +export type MessageChannelMessageAssociation = { + __typename: 'MessageChannelMessageAssociation'; + id: string; + messageId: string; + messageChannelId: string; + messageThreadExternalId: string; +}; diff --git a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts index 2255c8cb5..6e0d13f48 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts @@ -29,4 +29,5 @@ export enum CoreObjectNameSingular { WorkspaceMember = 'workspaceMember', MessageThreadSubscriber = 'messageThreadSubscriber', Workflow = 'workflow', + MessageChannelMessageAssociation = 'messageChannelMessageAssociation', }