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',
}