added "reply in gmail" button (#6754)
Issue https://github.com/twentyhq/twenty/issues/4217
This commit is contained in:
@ -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 (
|
||||
<StyledContainer>
|
||||
{loading ? (
|
||||
{threadLoading ? (
|
||||
<EmailLoader loadingText="Loading thread" />
|
||||
) : (
|
||||
<>
|
||||
@ -110,11 +144,21 @@ export const RightDrawerEmailThread = () => {
|
||||
isExpanded
|
||||
/>
|
||||
<CustomResolverFetchMoreLoader
|
||||
loading={loading}
|
||||
loading={threadLoading}
|
||||
onLastRowVisible={fetchMoreMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{canReply && !messageChannelLoading ? (
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
onClick={handleReplyClick}
|
||||
title="Reply (View in Gmail)"
|
||||
Icon={IconArrowBackUp}
|
||||
disabled={!canReply}
|
||||
></Button>
|
||||
</StyledButtonContainer>
|
||||
) : null}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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<string | null>(null);
|
||||
const [lastMessageChannelId, setLastMessageChannelId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [isMessagesFetchComplete, setIsMessagesFetchComplete] = useState(false);
|
||||
|
||||
const { record: thread } = useFindOneRecord<EmailThread>({
|
||||
objectNameSingular: CoreObjectNameSingular.MessageThread,
|
||||
@ -38,8 +46,9 @@ export const useRightDrawerEmailThread = () => {
|
||||
|
||||
const {
|
||||
records: messages,
|
||||
loading,
|
||||
loading: messagesLoading,
|
||||
fetchMoreRecords,
|
||||
hasNextPage,
|
||||
} = useFindManyRecords<EmailThreadMessage>({
|
||||
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<MessageChannelMessageAssociation>({
|
||||
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<MessageChannel>({
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export type MessageChannelMessageAssociation = {
|
||||
__typename: 'MessageChannelMessageAssociation';
|
||||
id: string;
|
||||
messageId: string;
|
||||
messageChannelId: string;
|
||||
messageThreadExternalId: string;
|
||||
};
|
||||
@ -29,4 +29,5 @@ export enum CoreObjectNameSingular {
|
||||
WorkspaceMember = 'workspaceMember',
|
||||
MessageThreadSubscriber = 'messageThreadSubscriber',
|
||||
Workflow = 'workflow',
|
||||
MessageChannelMessageAssociation = 'messageChannelMessageAssociation',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user