Add email thread widget and message thread record page layout (#19351)
## Summary - Move email thread display from side panel to a dedicated record page with a new `EMAIL_THREAD` widget type - Add message thread as a standard object with page layout, subject field, and backfill command - Add reply-to-email command menu item for message thread records - Remove old side panel message thread components in favor of the new widget-based approach ## Type fixes - Add `EMAIL_THREAD` to `WidgetConfigurationType`, `WidgetType`, and all configuration/validator maps - Create `EmailThreadConfigurationDTO` and shared `EmailThreadConfiguration` type - Register EMAIL_THREAD in widget type validators, configuration resolvers, and standard widget mappings ## Test plan - [ ] Verify message thread record pages render with the email thread widget - [ ] Verify email thread preview navigates to the record page instead of opening side panel - [ ] Verify reply-to-email command appears for message thread records - [ ] Verify typecheck passes for both twenty-front and twenty-server - [ ] Run existing test suites to check for regressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
@@ -40,6 +40,7 @@ import { TestWorkflowSingleRecordCommand } from '@/command-menu-item/engine-comm
|
||||
import { TidyUpWorkflowSingleRecordCommand } from '@/command-menu-item/engine-command/record/single-record/workflow/components/TidyUpWorkflowSingleRecordCommand';
|
||||
import { HeadlessFrontComponentRendererEngineCommand } from '@/command-menu-item/engine-command/components/HeadlessFrontComponentRendererEngineCommand';
|
||||
import { TriggerWorkflowVersionEngineCommand } from '@/command-menu-item/engine-command/record/components/TriggerWorkflowVersionEngineCommand';
|
||||
import { ReplyToEmailThreadCommand } from '@/command-menu-item/engine-command/record/single-record/message-thread/components/ReplyToEmailThreadCommand';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { AppPath, SettingsPath, SidePanelPages } from 'twenty-shared/types';
|
||||
@@ -240,6 +241,7 @@ export const ENGINE_COMPONENT_KEY_COMPONENT_MAP: Record<
|
||||
[EngineComponentKey.FRONT_COMPONENT_RENDERER]: (
|
||||
<HeadlessFrontComponentRendererEngineCommand />
|
||||
),
|
||||
[EngineComponentKey.REPLY_TO_EMAIL_THREAD]: <ReplyToEmailThreadCommand />,
|
||||
|
||||
// Deprecated keys kept for backward compatibility until migration runs
|
||||
[EngineComponentKey.DELETE_SINGLE_RECORD]: <DeleteRecordsCommand />,
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
||||
import { HeadlessEngineCommandWrapperEffect } from '@/command-menu-item/engine-command/components/HeadlessEngineCommandWrapperEffect';
|
||||
import { useHeadlessCommandContextApi } from '@/command-menu-item/engine-command/hooks/useHeadlessCommandContextApi';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const ALLOWED_REPLY_PROVIDERS = [
|
||||
ConnectedAccountProvider.GOOGLE,
|
||||
ConnectedAccountProvider.MICROSOFT,
|
||||
ConnectedAccountProvider.IMAP_SMTP_CALDAV,
|
||||
];
|
||||
|
||||
export const ReplyToEmailThreadCommand = () => {
|
||||
const { selectedRecords } = useHeadlessCommandContextApi();
|
||||
const threadId = selectedRecords[0]?.id ?? null;
|
||||
|
||||
const {
|
||||
messageThreadExternalId,
|
||||
connectedAccountHandle,
|
||||
connectedAccountProvider,
|
||||
lastMessageExternalId,
|
||||
connectedAccountConnectionParameters,
|
||||
messageChannelLoading,
|
||||
} = useEmailThread(threadId);
|
||||
|
||||
const canReply = useMemo(() => {
|
||||
return (
|
||||
isDefined(connectedAccountHandle) &&
|
||||
isDefined(connectedAccountProvider) &&
|
||||
ALLOWED_REPLY_PROVIDERS.includes(connectedAccountProvider) &&
|
||||
(connectedAccountProvider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV ||
|
||||
isDefined(connectedAccountConnectionParameters?.SMTP)) &&
|
||||
isDefined(messageThreadExternalId)
|
||||
);
|
||||
}, [
|
||||
connectedAccountConnectionParameters,
|
||||
connectedAccountHandle,
|
||||
connectedAccountProvider,
|
||||
messageThreadExternalId,
|
||||
]);
|
||||
|
||||
const handleExecute = () => {
|
||||
if (!canReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (connectedAccountProvider) {
|
||||
case ConnectedAccountProvider.MICROSOFT: {
|
||||
const url = `https://outlook.office.com/mail/deeplink?ItemID=${lastMessageExternalId}`;
|
||||
window.open(url, '_blank');
|
||||
break;
|
||||
}
|
||||
case ConnectedAccountProvider.GOOGLE: {
|
||||
const url = `https://mail.google.com/mail/?authuser=${connectedAccountHandle}#all/${messageThreadExternalId}`;
|
||||
window.open(url, '_blank');
|
||||
break;
|
||||
}
|
||||
case ConnectedAccountProvider.IMAP_SMTP_CALDAV:
|
||||
case ConnectedAccountProvider.OIDC:
|
||||
case ConnectedAccountProvider.SAML:
|
||||
case null:
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const isReady = !messageChannelLoading && canReply;
|
||||
|
||||
return (
|
||||
<HeadlessEngineCommandWrapperEffect
|
||||
execute={handleExecute}
|
||||
ready={isReady}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user