From 3fb29db28a25e14082e8e22775991a4266fc953e Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:18:30 +0530 Subject: [PATCH] Feat/email settings v2 (#23180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings pages changes - Add `displayName` - Unsubscribers Page --------- Co-authored-by: Félix Malfait --- .../src/metadata/generated/schema.graphql | 43 +- .../src/metadata/generated/schema.ts | 79 ++- .../src/metadata/generated/types.ts | 670 ++++++++++-------- .../src/generated-metadata/graphql.ts | 75 +- .../modules/accounts/types/MessageChannel.ts | 1 + .../modules/app/components/SettingsRoutes.tsx | 12 + .../SettingsAccountsNewEmailGroupChannel.tsx | 29 +- .../mutations/updateEmailGroupChannel.ts | 10 + .../graphql/queries/getMyMessageChannels.ts | 1 + .../hooks/useCreateEmailGroupChannel.ts | 5 +- .../hooks/useUpdateEmailGroupChannel.ts | 32 + .../components/SettingsPaginationControls.tsx | 55 ++ .../components/SettingsTableListSection.tsx | 29 +- .../queries/getUnsubscribePagePreviewUrl.ts | 7 - .../components/SettingsUnsubscribePreview.tsx | 107 +++ .../components/SettingsUnsubscribersList.tsx | 196 +++++ .../SettingsUnsubscribersFilterDropdown.tsx | 123 ++++ ...SettingsUnsubscribersFilterMenuContent.tsx | 60 ++ ...tingsUnsubscribersFilterOptionsContent.tsx | 57 ++ .../SettingsUnsubscribersFilterContentId.ts | 1 + .../SettingsUnsubscribersFilterOption.ts | 4 + .../constants/MessageSuppressionsPageSize.ts | 1 + .../constants/SettingsUnsubscribeTabIds.ts | 5 + .../SettingsUnsubscribersAllFilter.ts | 1 + .../graphql/queries/getMessageSuppressions.ts | 16 + .../hooks/useMessageSuppressions.ts | 43 ++ .../getMessageSuppressionReasonBadge.test.ts | 22 + .../utils/getMessageSuppressionReasonBadge.ts | 23 + ...rkspaceCommunicationGroupChannelDetail.tsx | 48 +- .../SettingsWorkspaceCommunications.tsx | 41 +- .../SettingsWorkspaceNewUnsubscribeTopic.tsx | 27 +- .../SettingsWorkspaceUnsubscribe.tsx | 84 +++ ...ettingsWorkspaceUnsubscribeTopicDetail.tsx | 18 +- ...030252-add-message-channel-display-name.ts | 21 + .../instance-commands.constant.ts | 2 + .../dtos/find-message-suppressions.input.ts | 40 ++ .../dtos/message-suppression.dto.ts | 43 ++ .../dtos/create-email-group-channel.input.ts | 14 +- .../dtos/message-channel.dto.ts | 5 + .../dtos/update-email-group-channel.input.ts | 16 + .../entities/message-channel.entity.ts | 3 + .../message-channel-metadata.service.ts | 49 ++ .../resolvers/message-channel.resolver.ts | 17 + .../src/modules/emailing/emailing.module.ts | 2 + .../resolvers/message-suppression.resolver.ts | 58 ++ .../resolvers/unsubscribe-topic.resolver.ts | 22 - .../emailing-domain-sender.service.ts | 35 +- .../services/message-suppression.service.ts | 49 +- .../twenty-shared/src/types/SettingsPath.ts | 1 + 49 files changed, 1913 insertions(+), 389 deletions(-) create mode 100644 packages/twenty-front/src/modules/settings/accounts/graphql/mutations/updateEmailGroupChannel.ts create mode 100644 packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateEmailGroupChannel.ts create mode 100644 packages/twenty-front/src/modules/settings/components/SettingsPaginationControls.tsx delete mode 100644 packages/twenty-front/src/modules/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribePreview.tsx create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribersList.tsx create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterDropdown.tsx create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterMenuContent.tsx create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterOptionsContent.tsx create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterContentId.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterOption.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/constants/MessageSuppressionsPageSize.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribeTabIds.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribersAllFilter.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/graphql/queries/getMessageSuppressions.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/hooks/useMessageSuppressions.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/utils/__tests__/getMessageSuppressionReasonBadge.test.ts create mode 100644 packages/twenty-front/src/modules/settings/unsubscribers/utils/getMessageSuppressionReasonBadge.ts create mode 100644 packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribe.tsx create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-instance-command-fast-1784904030252-add-message-channel-display-name.ts create mode 100644 packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/find-message-suppressions.input.ts create mode 100644 packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/message-suppression.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/update-email-group-channel.input.ts create mode 100644 packages/twenty-server/src/modules/emailing/resolvers/message-suppression.resolver.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index e9eb790102..63cd099d1e 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2499,6 +2499,7 @@ type MessageChannel { id: UUID! visibility: MessageChannelVisibility! handle: String! + displayName: String type: MessageChannelType! isContactAutoCreationEnabled: Boolean! contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy! @@ -2597,6 +2598,31 @@ type SendMessageCampaignOutputDTO { skipped: CampaignSkippedRecipientsDTO! } +type MessageSuppression { + id: UUID! + createdAt: DateTime! + emailAddress: String! + reason: MessageSuppressionReason! + source: MessageSuppressionSource! + unsubscribeTopicId: UUID +} + +enum MessageSuppressionReason { + BOUNCE + COMPLAINT + UNSUBSCRIBE +} + +enum MessageSuppressionSource { + WEBHOOK + SYSTEM +} + +type MessageSuppressionList { + records: [MessageSuppression!]! + totalCount: Int! +} + type UnsubscribeTopic { id: UUID! createdAt: DateTime! @@ -3157,8 +3183,8 @@ type Query { getViewGroup(id: String!): ViewGroup getRoles: [Role!]! previewMessageCampaignAudience(input: PreviewMessageCampaignAudienceInput!): CampaignAudiencePreviewDTO! + messageSuppressions(input: FindMessageSuppressionsInput!): MessageSuppressionList! unsubscribeTopics: [UnsubscribeTopic!]! - unsubscribePagePreviewUrl: String! myMessageChannels(connectedAccountId: UUID): [MessageChannel!]! getEmailingDomains: [EmailingDomain!]! myConnectedAccounts: [ConnectedAccountPublicDTO!]! @@ -3232,6 +3258,14 @@ input PreviewMessageCampaignAudienceInput { unsubscribeTopicId: String } +input FindMessageSuppressionsInput { + reason: MessageSuppressionReason + searchTerm: String + unsubscribeTopicId: UUID + limit: Int! = 30 + offset: Int! = 0 +} + input ListAppConnectionsInput { providerName: String userWorkspaceId: String @@ -3448,6 +3482,7 @@ type Mutation { deleteUnsubscribeTopic(id: String!): Boolean! updateMessageChannel(input: UpdateMessageChannelInput!): MessageChannel! createEmailGroupChannel(input: CreateEmailGroupChannelInput!): CreateEmailGroupChannelOutput! + updateEmailGroupChannel(input: UpdateEmailGroupChannelInput!): MessageChannel! deleteEmailGroupChannel(id: UUID!): MessageChannel! createEmailingDomain(input: CreateEmailingDomainInput!): EmailingDomain! deleteEmailingDomain(id: String!): Boolean! @@ -4532,6 +4567,12 @@ input UpdateMessageChannelInputUpdates { input CreateEmailGroupChannelInput { handle: String! + displayName: String +} + +input UpdateEmailGroupChannelInput { + id: UUID! + displayName: String } input CreateEmailingDomainInput { diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 09802f1971..11f21c72fc 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -2201,6 +2201,7 @@ export interface MessageChannel { id: Scalars['UUID'] visibility: MessageChannelVisibility handle: Scalars['String'] + displayName?: Scalars['String'] type: MessageChannelType isContactAutoCreationEnabled: Scalars['Boolean'] contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy @@ -2271,6 +2272,26 @@ export interface SendMessageCampaignOutputDTO { __typename: 'SendMessageCampaignOutputDTO' } +export interface MessageSuppression { + id: Scalars['UUID'] + createdAt: Scalars['DateTime'] + emailAddress: Scalars['String'] + reason: MessageSuppressionReason + source: MessageSuppressionSource + unsubscribeTopicId?: Scalars['UUID'] + __typename: 'MessageSuppression' +} + +export type MessageSuppressionReason = 'BOUNCE' | 'COMPLAINT' | 'UNSUBSCRIBE' + +export type MessageSuppressionSource = 'WEBHOOK' | 'SYSTEM' + +export interface MessageSuppressionList { + records: MessageSuppression[] + totalCount: Scalars['Int'] + __typename: 'MessageSuppressionList' +} + export interface UnsubscribeTopic { id: Scalars['UUID'] createdAt: Scalars['DateTime'] @@ -2793,8 +2814,8 @@ export interface Query { getViewGroup?: ViewGroup getRoles: Role[] previewMessageCampaignAudience: CampaignAudiencePreviewDTO + messageSuppressions: MessageSuppressionList unsubscribeTopics: UnsubscribeTopic[] - unsubscribePagePreviewUrl: Scalars['String'] myMessageChannels: MessageChannel[] getEmailingDomains: EmailingDomain[] myConnectedAccounts: ConnectedAccountPublicDTO[] @@ -2992,6 +3013,7 @@ export interface Mutation { deleteUnsubscribeTopic: Scalars['Boolean'] updateMessageChannel: MessageChannel createEmailGroupChannel: CreateEmailGroupChannelOutput + updateEmailGroupChannel: MessageChannel deleteEmailGroupChannel: MessageChannel createEmailingDomain: EmailingDomain deleteEmailingDomain: Scalars['Boolean'] @@ -5396,6 +5418,7 @@ export interface MessageChannelGenqlSelection{ id?: boolean | number visibility?: boolean | number handle?: boolean | number + displayName?: boolean | number type?: boolean | number isContactAutoCreationEnabled?: boolean | number contactAutoCreationPolicy?: boolean | number @@ -5458,6 +5481,24 @@ export interface SendMessageCampaignOutputDTOGenqlSelection{ __scalar?: boolean | number } +export interface MessageSuppressionGenqlSelection{ + id?: boolean | number + createdAt?: boolean | number + emailAddress?: boolean | number + reason?: boolean | number + source?: boolean | number + unsubscribeTopicId?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface MessageSuppressionListGenqlSelection{ + records?: MessageSuppressionGenqlSelection + totalCount?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface UnsubscribeTopicGenqlSelection{ id?: boolean | number createdAt?: boolean | number @@ -6020,8 +6061,8 @@ export interface QueryGenqlSelection{ getViewGroup?: (ViewGroupGenqlSelection & { __args: {id: Scalars['String']} }) getRoles?: RoleGenqlSelection previewMessageCampaignAudience?: (CampaignAudiencePreviewDTOGenqlSelection & { __args: {input: PreviewMessageCampaignAudienceInput} }) + messageSuppressions?: (MessageSuppressionListGenqlSelection & { __args: {input: FindMessageSuppressionsInput} }) unsubscribeTopics?: UnsubscribeTopicGenqlSelection - unsubscribePagePreviewUrl?: boolean | number myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) getEmailingDomains?: EmailingDomainGenqlSelection myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection @@ -6079,6 +6120,8 @@ id: Scalars['ID']} export interface PreviewMessageCampaignAudienceInput {listId: Scalars['String'],unsubscribeTopicId?: (Scalars['String'] | null)} +export interface FindMessageSuppressionsInput {reason?: (MessageSuppressionReason | null),searchTerm?: (Scalars['String'] | null),unsubscribeTopicId?: (Scalars['UUID'] | null),limit: Scalars['Int'],offset: Scalars['Int']} + export interface ListAppConnectionsInput {providerName?: (Scalars['String'] | null),userWorkspaceId?: (Scalars['String'] | null),visibility?: (Scalars['String'] | null)} export interface EventLogQueryInput {table: EventLogTable,filters?: (EventLogFiltersInput | null),first?: (Scalars['Int'] | null),after?: (Scalars['String'] | null)} @@ -6246,6 +6289,7 @@ export interface MutationGenqlSelection{ deleteUnsubscribeTopic?: { __args: {id: Scalars['String']} } updateMessageChannel?: (MessageChannelGenqlSelection & { __args: {input: UpdateMessageChannelInput} }) createEmailGroupChannel?: (CreateEmailGroupChannelOutputGenqlSelection & { __args: {input: CreateEmailGroupChannelInput} }) + updateEmailGroupChannel?: (MessageChannelGenqlSelection & { __args: {input: UpdateEmailGroupChannelInput} }) deleteEmailGroupChannel?: (MessageChannelGenqlSelection & { __args: {id: Scalars['UUID']} }) createEmailingDomain?: (EmailingDomainGenqlSelection & { __args: {input: CreateEmailingDomainInput} }) deleteEmailingDomain?: { __args: {id: Scalars['String']} } @@ -6655,7 +6699,9 @@ export interface UpdateMessageChannelInput {id: Scalars['UUID'],update: UpdateMe export interface UpdateMessageChannelInputUpdates {visibility?: (MessageChannelVisibility | null),isContactAutoCreationEnabled?: (Scalars['Boolean'] | null),contactAutoCreationPolicy?: (MessageChannelContactAutoCreationPolicy | null),messageFolderImportPolicy?: (MessageFolderImportPolicy | null),isSyncEnabled?: (Scalars['Boolean'] | null),excludeNonProfessionalEmails?: (Scalars['Boolean'] | null),excludeGroupEmails?: (Scalars['Boolean'] | null)} -export interface CreateEmailGroupChannelInput {handle: Scalars['String']} +export interface CreateEmailGroupChannelInput {handle: Scalars['String'],displayName?: (Scalars['String'] | null)} + +export interface UpdateEmailGroupChannelInput {id: Scalars['UUID'],displayName?: (Scalars['String'] | null)} export interface CreateEmailingDomainInput {domain: Scalars['String']} @@ -8409,6 +8455,22 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const MessageSuppression_possibleTypes: string[] = ['MessageSuppression'] + export const isMessageSuppression = (obj?: { __typename?: any } | null): obj is MessageSuppression => { + if (!obj?.__typename) throw new Error('__typename is missing in "isMessageSuppression"') + return MessageSuppression_possibleTypes.includes(obj.__typename) + } + + + + const MessageSuppressionList_possibleTypes: string[] = ['MessageSuppressionList'] + export const isMessageSuppressionList = (obj?: { __typename?: any } | null): obj is MessageSuppressionList => { + if (!obj?.__typename) throw new Error('__typename is missing in "isMessageSuppressionList"') + return MessageSuppressionList_possibleTypes.includes(obj.__typename) + } + + + const UnsubscribeTopic_possibleTypes: string[] = ['UnsubscribeTopic'] export const isUnsubscribeTopic = (obj?: { __typename?: any } | null): obj is UnsubscribeTopic => { if (!obj?.__typename) throw new Error('__typename is missing in "isUnsubscribeTopic"') @@ -9386,6 +9448,17 @@ export const enumMessageChannelSyncStage = { FAILED: 'FAILED' as const } +export const enumMessageSuppressionReason = { + BOUNCE: 'BOUNCE' as const, + COMPLAINT: 'COMPLAINT' as const, + UNSUBSCRIBE: 'UNSUBSCRIBE' as const +} + +export const enumMessageSuppressionSource = { + WEBHOOK: 'WEBHOOK' as const, + SYSTEM: 'SYSTEM' as const +} + export const enumUnsubscribeTopicVisibility = { PUBLIC: 'PUBLIC' as const, PRIVATE: 'PRIVATE' as const diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 87e318286e..fd6266af13 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -72,18 +72,20 @@ export default { 279, 280, 287, - 327, - 329, - 330, + 288, + 291, 331, - 332, + 333, 334, + 335, 336, - 348, - 355, - 362, - 363, - 492 + 338, + 340, + 353, + 360, + 367, + 368, + 498 ], "types": { "BillingProductDTO": { @@ -4975,6 +4977,9 @@ export default { "handle": [ 1 ], + "displayName": [ + 1 + ], "type": [ 275 ], @@ -5110,6 +5115,42 @@ export default { 1 ] }, + "MessageSuppression": { + "id": [ + 4 + ], + "createdAt": [ + 6 + ], + "emailAddress": [ + 1 + ], + "reason": [ + 287 + ], + "source": [ + 288 + ], + "unsubscribeTopicId": [ + 4 + ], + "__typename": [ + 1 + ] + }, + "MessageSuppressionReason": {}, + "MessageSuppressionSource": {}, + "MessageSuppressionList": { + "records": [ + 286 + ], + "totalCount": [ + 30 + ], + "__typename": [ + 1 + ] + }, "UnsubscribeTopic": { "id": [ 4 @@ -5127,7 +5168,7 @@ export default { 1 ], "visibility": [ - 287 + 291 ], "__typename": [ 1 @@ -5173,7 +5214,7 @@ export default { 1 ], "location": [ - 289 + 293 ], "__typename": [ 1 @@ -5201,13 +5242,13 @@ export default { 1 ], "IMAP": [ - 291 + 295 ], "SMTP": [ - 291 + 295 ], "CALDAV": [ - 291 + 295 ], "__typename": [ 1 @@ -5227,7 +5268,7 @@ export default { 4 ], "connectionParameters": [ - 292 + 296 ], "__typename": [ 1 @@ -5449,7 +5490,7 @@ export default { 1 ], "series": [ - 301 + 305 ], "xAxisLabel": [ 1 @@ -5498,7 +5539,7 @@ export default { 1 ], "data": [ - 303 + 307 ], "__typename": [ 1 @@ -5506,7 +5547,7 @@ export default { }, "LineChartData": { "series": [ - 304 + 308 ], "xAxisLabel": [ 1 @@ -5543,7 +5584,7 @@ export default { }, "PieChartData": { "data": [ - 306 + 310 ], "showLegend": [ 3 @@ -5648,13 +5689,13 @@ export default { }, "EventLogQueryResult": { "records": [ - 311 + 315 ], "totalCount": [ 30 ], "pageInfo": [ - 312 + 316 ], "__typename": [ 1 @@ -5718,7 +5759,7 @@ export default { 1 ], "parts": [ - 297 + 301 ], "processedAt": [ 6 @@ -5787,7 +5828,7 @@ export default { }, "AiSystemPromptPreview": { "sections": [ - 317 + 321 ], "estimatedTokenCount": [ 30 @@ -5815,7 +5856,7 @@ export default { 30 ], "error": [ - 319 + 323 ], "__typename": [ 1 @@ -5877,10 +5918,10 @@ export default { 4 ], "evaluations": [ - 323 + 327 ], "messages": [ - 315 + 319 ], "createdAt": [ 6 @@ -5911,7 +5952,7 @@ export default { 5 ], "scope": [ - 327 + 331 ], "__typename": [ 1 @@ -5926,19 +5967,19 @@ export default { 1 ], "syncStatus": [ - 329 + 333 ], "syncStage": [ - 330 + 334 ], "visibility": [ - 331 + 335 ], "isContactAutoCreationEnabled": [ 3 ], "contactAutoCreationPolicy": [ - 332 + 336 ], "isSyncEnabled": [ 3 @@ -5989,7 +6030,7 @@ export default { 1 ], "pendingSyncAction": [ - 334 + 338 ], "messageChannelId": [ 4 @@ -6007,7 +6048,7 @@ export default { "MessageFolderPendingSyncAction": {}, "CollectionHash": { "collectionName": [ - 336 + 340 ], "hash": [ 1 @@ -6071,13 +6112,13 @@ export default { }, "MinimalMetadata": { "objectMetadataItems": [ - 337 + 341 ], "views": [ - 338 + 342 ], "collectionHashes": [ - 335 + 339 ], "__typename": [ 1 @@ -6251,7 +6292,7 @@ export default { 7, { "input": [ - 341, + 345, "GetApiKeyInput!" ] } @@ -6354,7 +6395,7 @@ export default { 11, { "input": [ - 342, + 346, "AgentIdInput!" ] } @@ -6388,7 +6429,7 @@ export default { "CursorPaging!" ], "filter": [ - 343, + 347, "ObjectFilter!" ] } @@ -6397,7 +6438,7 @@ export default { 22, { "input": [ - 344, + 348, "LogicFunctionIdInput!" ] } @@ -6409,7 +6450,7 @@ export default { 5, { "input": [ - 344, + 348, "LogicFunctionIdInput!" ] } @@ -6418,7 +6459,7 @@ export default { 1, { "input": [ - 344, + 348, "LogicFunctionIdInput!" ] } @@ -6640,16 +6681,22 @@ export default { 282, { "input": [ - 345, + 349, "PreviewMessageCampaignAudienceInput!" ] } ], - "unsubscribeTopics": [ - 286 + "messageSuppressions": [ + 289, + { + "input": [ + 350, + "FindMessageSuppressionsInput!" + ] + } ], - "unsubscribePagePreviewUrl": [ - 1 + "unsubscribeTopics": [ + 290 ], "myMessageChannels": [ 273, @@ -6666,7 +6713,7 @@ export default { 240 ], "getToolIndex": [ - 296 + 300 ], "getToolInputSchema": [ 5, @@ -6678,10 +6725,10 @@ export default { } ], "webhooks": [ - 295 + 299 ], "webhook": [ - 295, + 299, { "id": [ 4, @@ -6690,7 +6737,7 @@ export default { } ], "myMessageFolders": [ - 333, + 337, { "messageChannelId": [ 4 @@ -6698,7 +6745,7 @@ export default { } ], "myCalendarChannels": [ - 328, + 332, { "connectedAccountId": [ 4 @@ -6706,17 +6753,17 @@ export default { } ], "minimalMetadata": [ - 339 + 343 ], "appKeyValue": [ - 326, + 330, { "key": [ 1, "String!" ], "scope": [ - 327 + 331 ] } ], @@ -6724,7 +6771,7 @@ export default { 201, { "filter": [ - 346 + 351 ] } ], @@ -6738,13 +6785,13 @@ export default { } ], "findWorkspaceAiStats": [ - 325 + 329 ], "chatThreads": [ - 316 + 320 ], "chatThread": [ - 316, + 320, { "id": [ 4, @@ -6753,7 +6800,7 @@ export default { } ], "chatMessages": [ - 315, + 319, { "threadId": [ 4, @@ -6762,7 +6809,7 @@ export default { } ], "chatStreamCatchupChunks": [ - 320, + 324, { "threadId": [ 4, @@ -6771,13 +6818,13 @@ export default { } ], "getAiSystemPromptPreview": [ - 318 + 322 ], "skills": [ - 314 + 318 ], "skill": [ - 314, + 318, { "id": [ 4, @@ -6786,7 +6833,7 @@ export default { } ], "agentTurns": [ - 324, + 328, { "agentId": [ 4, @@ -6852,43 +6899,43 @@ export default { 207 ], "eventLogs": [ - 313, + 317, { "input": [ - 347, + 352, "EventLogQueryInput!" ] } ], "pieChartData": [ - 307, + 311, { "input": [ - 351, + 356, "PieChartDataInput!" ] } ], "lineChartData": [ - 305, + 309, { "input": [ - 352, + 357, "LineChartDataInput!" ] } ], "barChartData": [ - 302, + 306, { "input": [ - 353, + 358, "BarChartDataInput!" ] } ], "getConnectedImapSmtpCaldavAccount": [ - 293, + 297, { "id": [ 4, @@ -6897,7 +6944,7 @@ export default { } ], "getAutoCompleteAddress": [ - 288, + 292, { "address": [ 1, @@ -6916,7 +6963,7 @@ export default { } ], "getAddressDetails": [ - 290, + 294, { "placeId": [ 1, @@ -6932,7 +6979,7 @@ export default { 265, { "input": [ - 354 + 359 ] } ], @@ -6961,10 +7008,10 @@ export default { }, "ObjectFilter": { "and": [ - 343 + 347 ], "or": [ - 343 + 347 ], "id": [ 33 @@ -7013,6 +7060,26 @@ export default { 1 ] }, + "FindMessageSuppressionsInput": { + "reason": [ + 287 + ], + "searchTerm": [ + 1 + ], + "unsubscribeTopicId": [ + 4 + ], + "limit": [ + 30 + ], + "offset": [ + 30 + ], + "__typename": [ + 1 + ] + }, "ListAppConnectionsInput": { "providerName": [ 1 @@ -7029,10 +7096,10 @@ export default { }, "EventLogQueryInput": { "table": [ - 348 + 353 ], "filters": [ - 349 + 354 ], "first": [ 30 @@ -7053,7 +7120,7 @@ export default { 1 ], "dateRange": [ - 350 + 355 ], "recordId": [ 1 @@ -7120,7 +7187,7 @@ export default { 1 ], "operationTypes": [ - 355 + 360 ], "__typename": [ 1 @@ -7132,7 +7199,7 @@ export default { 3, { "input": [ - 357, + 362, "AddQuerySubscriptionInput!" ] } @@ -7141,7 +7208,7 @@ export default { 3, { "input": [ - 358, + 363, "RemoveQueryFromEventStreamInput!" ] } @@ -7150,7 +7217,7 @@ export default { 159, { "inputs": [ - 359, + 364, "[CreateNavigationMenuItemInput!]!" ] } @@ -7159,7 +7226,7 @@ export default { 159, { "input": [ - 359, + 364, "CreateNavigationMenuItemInput!" ] } @@ -7168,7 +7235,7 @@ export default { 159, { "inputs": [ - 360, + 365, "[UpdateOneNavigationMenuItemInput!]!" ] } @@ -7177,7 +7244,7 @@ export default { 159, { "input": [ - 360, + 365, "UpdateOneNavigationMenuItemInput!" ] } @@ -7212,7 +7279,7 @@ export default { "Float!" ], "fileFolder": [ - 362, + 367, "FileFolder!" ], "fieldMetadataId": [ @@ -7251,7 +7318,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ] } @@ -7260,7 +7327,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ] } @@ -7269,7 +7336,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ] } @@ -7278,7 +7345,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ] } @@ -7287,7 +7354,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ] } @@ -7296,7 +7363,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ], "fieldMetadataId": [ @@ -7309,7 +7376,7 @@ export default { 146, { "file": [ - 363, + 368, "Upload!" ], "fieldMetadataUniversalIdentifier": [ @@ -7322,7 +7389,7 @@ export default { 56, { "input": [ - 364, + 369, "CreateViewFilterGroupInput!" ] } @@ -7335,7 +7402,7 @@ export default { "String!" ], "input": [ - 365, + 370, "UpdateViewFilterGroupInput!" ] } @@ -7362,7 +7429,7 @@ export default { 58, { "input": [ - 366, + 371, "CreateViewFilterInput!" ] } @@ -7371,7 +7438,7 @@ export default { 58, { "input": [ - 367, + 372, "UpdateViewFilterInput!" ] } @@ -7380,7 +7447,7 @@ export default { 58, { "input": [ - 369, + 374, "DeleteViewFilterInput!" ] } @@ -7389,7 +7456,7 @@ export default { 58, { "input": [ - 370, + 375, "DestroyViewFilterInput!" ] } @@ -7398,7 +7465,7 @@ export default { 64, { "input": [ - 371, + 376, "CreateViewInput!" ] } @@ -7411,7 +7478,7 @@ export default { "String!" ], "input": [ - 372, + 377, "UpdateViewInput!" ] } @@ -7438,7 +7505,7 @@ export default { 64, { "input": [ - 373, + 378, "UpsertViewWidgetInput!" ] } @@ -7447,7 +7514,7 @@ export default { 61, { "input": [ - 379, + 384, "CreateViewSortInput!" ] } @@ -7456,7 +7523,7 @@ export default { 61, { "input": [ - 380, + 385, "UpdateViewSortInput!" ] } @@ -7465,7 +7532,7 @@ export default { 3, { "input": [ - 382, + 387, "DeleteViewSortInput!" ] } @@ -7474,7 +7541,7 @@ export default { 3, { "input": [ - 383, + 388, "DestroyViewSortInput!" ] } @@ -7483,7 +7550,7 @@ export default { 54, { "input": [ - 384, + 389, "UpdateViewFieldInput!" ] } @@ -7492,7 +7559,7 @@ export default { 54, { "input": [ - 386, + 391, "CreateViewFieldInput!" ] } @@ -7501,7 +7568,7 @@ export default { 54, { "inputs": [ - 386, + 391, "[CreateViewFieldInput!]!" ] } @@ -7510,7 +7577,7 @@ export default { 54, { "input": [ - 387, + 392, "DeleteViewFieldInput!" ] } @@ -7519,7 +7586,7 @@ export default { 54, { "input": [ - 388, + 393, "DestroyViewFieldInput!" ] } @@ -7528,7 +7595,7 @@ export default { 63, { "input": [ - 389, + 394, "UpdateViewFieldGroupInput!" ] } @@ -7537,7 +7604,7 @@ export default { 63, { "input": [ - 391, + 396, "CreateViewFieldGroupInput!" ] } @@ -7546,7 +7613,7 @@ export default { 63, { "inputs": [ - 391, + 396, "[CreateViewFieldGroupInput!]!" ] } @@ -7555,7 +7622,7 @@ export default { 63, { "input": [ - 392, + 397, "DeleteViewFieldGroupInput!" ] } @@ -7564,7 +7631,7 @@ export default { 63, { "input": [ - 393, + 398, "DestroyViewFieldGroupInput!" ] } @@ -7573,7 +7640,7 @@ export default { 64, { "input": [ - 394, + 399, "UpsertFieldsWidgetInput!" ] } @@ -7582,7 +7649,7 @@ export default { 7, { "input": [ - 397, + 402, "CreateApiKeyInput!" ] } @@ -7591,7 +7658,7 @@ export default { 7, { "input": [ - 398, + 403, "UpdateApiKeyInput!" ] } @@ -7600,7 +7667,7 @@ export default { 7, { "input": [ - 399, + 404, "RevokeApiKeyInput!" ] } @@ -7755,7 +7822,7 @@ export default { 145, { "input": [ - 400, + 405, "CreateApprovedAccessDomainInput!" ] } @@ -7764,7 +7831,7 @@ export default { 3, { "input": [ - 401, + 406, "DeleteApprovedAccessDomainInput!" ] } @@ -7773,7 +7840,7 @@ export default { 145, { "input": [ - 402, + 407, "ValidateApprovedAccessDomainInput!" ] } @@ -7782,7 +7849,7 @@ export default { 119, { "input": [ - 403, + 408, "CreatePageLayoutTabInput!" ] } @@ -7795,7 +7862,7 @@ export default { "String!" ], "input": [ - 404, + 409, "UpdatePageLayoutTabInput!" ] } @@ -7813,7 +7880,7 @@ export default { 120, { "input": [ - 405, + 410, "CreatePageLayoutInput!" ] } @@ -7826,7 +7893,7 @@ export default { "String!" ], "input": [ - 406, + 411, "UpdatePageLayoutInput!" ] } @@ -7848,7 +7915,7 @@ export default { "String!" ], "input": [ - 407, + 412, "UpdatePageLayoutWithTabsInput!" ] } @@ -7884,7 +7951,7 @@ export default { 81, { "input": [ - 411, + 416, "CreatePageLayoutWidgetInput!" ] } @@ -7897,7 +7964,7 @@ export default { "String!" ], "input": [ - 412, + 417, "UpdatePageLayoutWidgetInput!" ] } @@ -7915,7 +7982,7 @@ export default { 11, { "input": [ - 413, + 418, "CreateAgentInput!" ] } @@ -7924,7 +7991,7 @@ export default { 11, { "input": [ - 414, + 419, "UpdateAgentInput!" ] } @@ -7933,7 +8000,7 @@ export default { 11, { "input": [ - 342, + 346, "AgentIdInput!" ] } @@ -7942,7 +8009,7 @@ export default { 28, { "input": [ - 415, + 420, "CreateOneObjectInput!" ] } @@ -7951,7 +8018,7 @@ export default { 28, { "input": [ - 417, + 422, "DeleteOneObjectInput!" ] } @@ -7960,7 +8027,7 @@ export default { 28, { "input": [ - 418, + 423, "UpdateOneObjectInput!" ] } @@ -7969,7 +8036,7 @@ export default { 26, { "input": [ - 420, + 425, "CreateOneIndexInput!" ] } @@ -7978,7 +8045,7 @@ export default { 26, { "input": [ - 423, + 428, "DeleteOneIndexInput!" ] } @@ -7987,7 +8054,7 @@ export default { 22, { "input": [ - 344, + 348, "LogicFunctionIdInput!" ] } @@ -7996,7 +8063,7 @@ export default { 22, { "input": [ - 424, + 429, "CreateLogicFunctionFromSourceInput!" ] } @@ -8005,7 +8072,7 @@ export default { 141, { "input": [ - 425, + 430, "ExecuteOneLogicFunctionInput!" ] } @@ -8014,7 +8081,7 @@ export default { 3, { "input": [ - 426, + 431, "UpdateLogicFunctionFromSourceInput!" ] } @@ -8023,7 +8090,7 @@ export default { 15, { "input": [ - 428, + 433, "CreateCommandMenuItemInput!" ] } @@ -8032,7 +8099,7 @@ export default { 15, { "input": [ - 429, + 434, "UpdateCommandMenuItemInput!" ] } @@ -8059,7 +8126,7 @@ export default { 14, { "input": [ - 430, + 435, "CreateFrontComponentInput!" ] } @@ -8068,7 +8135,7 @@ export default { 14, { "input": [ - 431, + 436, "UpdateFrontComponentInput!" ] } @@ -8086,7 +8153,7 @@ export default { 70, { "data": [ - 433, + 438, "ActivateWorkspaceInput!" ] } @@ -8095,7 +8162,7 @@ export default { 70, { "data": [ - 434, + 439, "UpdateWorkspaceInput!" ] } @@ -8123,7 +8190,7 @@ export default { 198, { "input": [ - 435, + 440, "CreateApplicationRegistrationInput!" ] } @@ -8132,7 +8199,7 @@ export default { 76, { "input": [ - 436, + 441, "UpdateApplicationRegistrationInput!" ] } @@ -8159,7 +8226,7 @@ export default { 2, { "input": [ - 438, + 443, "CreateApplicationRegistrationVariableInput!" ] } @@ -8168,7 +8235,7 @@ export default { 2, { "input": [ - 439, + 444, "UpdateApplicationRegistrationVariableInput!" ] } @@ -8186,7 +8253,7 @@ export default { 76, { "file": [ - 363, + 368, "Upload!" ], "universalIdentifier": [ @@ -8248,7 +8315,7 @@ export default { "UUID!" ], "input": [ - 441, + 446, "UpdateApplicationInput!" ] } @@ -8269,7 +8336,7 @@ export default { 24, { "input": [ - 442, + 447, "CreateOneFieldMetadataInput!" ] } @@ -8278,7 +8345,7 @@ export default { 24, { "input": [ - 444, + 449, "UpdateOneFieldMetadataInput!" ] } @@ -8287,7 +8354,7 @@ export default { 24, { "input": [ - 446, + 451, "DeleteOneFieldInput!" ] } @@ -8296,7 +8363,7 @@ export default { 60, { "input": [ - 447, + 452, "CreateViewGroupInput!" ] } @@ -8305,7 +8372,7 @@ export default { 60, { "inputs": [ - 447, + 452, "[CreateViewGroupInput!]!" ] } @@ -8314,7 +8381,7 @@ export default { 60, { "input": [ - 448, + 453, "UpdateViewGroupInput!" ] } @@ -8323,7 +8390,7 @@ export default { 60, { "inputs": [ - 448, + 453, "[UpdateViewGroupInput!]!" ] } @@ -8332,7 +8399,7 @@ export default { 60, { "input": [ - 450, + 455, "DeleteViewGroupInput!" ] } @@ -8341,7 +8408,7 @@ export default { 60, { "input": [ - 451, + 456, "DestroyViewGroupInput!" ] } @@ -8363,7 +8430,7 @@ export default { 49, { "createRoleInput": [ - 452, + 457, "CreateRoleInput!" ] } @@ -8372,7 +8439,7 @@ export default { 49, { "updateRoleInput": [ - 453, + 458, "UpdateRoleInput!" ] } @@ -8390,7 +8457,7 @@ export default { 46, { "upsertObjectPermissionsInput": [ - 455, + 460, "UpsertObjectPermissionsInput!" ] } @@ -8399,7 +8466,7 @@ export default { 47, { "upsertPermissionFlagsInput": [ - 457, + 462, "UpsertPermissionFlagsInput!" ] } @@ -8408,7 +8475,7 @@ export default { 41, { "upsertFieldPermissionsInput": [ - 458, + 463, "UpsertFieldPermissionsInput!" ] } @@ -8417,7 +8484,7 @@ export default { 235, { "input": [ - 460, + 465, "UpsertRowLevelPermissionPredicatesInput!" ] } @@ -8448,7 +8515,7 @@ export default { 283, { "input": [ - 463, + 468, "SendEmailViaDomainInput!" ] } @@ -8457,25 +8524,25 @@ export default { 285, { "input": [ - 464, + 469, "SendMessageCampaignInput!" ] } ], "createUnsubscribeTopic": [ - 286, + 290, { "input": [ - 465, + 470, "CreateUnsubscribeTopicInput!" ] } ], "updateUnsubscribeTopic": [ - 286, + 290, { "input": [ - 466, + 471, "UpdateUnsubscribeTopicInput!" ] } @@ -8493,7 +8560,7 @@ export default { 273, { "input": [ - 467, + 472, "UpdateMessageChannelInput!" ] } @@ -8502,11 +8569,20 @@ export default { 281, { "input": [ - 469, + 474, "CreateEmailGroupChannelInput!" ] } ], + "updateEmailGroupChannel": [ + 273, + { + "input": [ + 475, + "UpdateEmailGroupChannelInput!" + ] + } + ], "deleteEmailGroupChannel": [ 273, { @@ -8520,7 +8596,7 @@ export default { 271, { "input": [ - 470, + 476, "CreateEmailingDomainInput!" ] } @@ -8553,34 +8629,34 @@ export default { } ], "runAgent": [ - 298, + 302, { "input": [ - 471, + 477, "RunAgentInput!" ] } ], "createWebhook": [ - 295, + 299, { "input": [ - 472, + 478, "CreateWebhookInput!" ] } ], "updateWebhook": [ - 295, + 299, { "input": [ - 473, + 479, "UpdateWebhookInput!" ] } ], "deleteWebhook": [ - 295, + 299, { "id": [ 4, @@ -8589,37 +8665,37 @@ export default { } ], "updateMessageFolder": [ - 333, + 337, { "input": [ - 475, + 481, "UpdateMessageFolderInput!" ] } ], "updateMessageFolders": [ - 333, + 337, { "input": [ - 477, + 483, "UpdateMessageFoldersInput!" ] } ], "updateCalendarChannel": [ - 328, + 332, { "input": [ - 478, + 484, "UpdateCalendarChannelInput!" ] } ], "setAppKeyValue": [ - 326, + 330, { "input": [ - 480, + 486, "SetAppKeyValueInput!" ] } @@ -8632,15 +8708,15 @@ export default { "String!" ], "scope": [ - 327 + 331 ] } ], "createChatThread": [ - 316 + 320 ], "sendChatMessage": [ - 321, + 325, { "threadId": [ 4, @@ -8661,13 +8737,13 @@ export default { 1 ], "fileAttachments": [ - 481, + 487, "[FileAttachmentInput!]" ] } ], "retryChatMessage": [ - 321, + 325, { "threadId": [ 4, @@ -8679,7 +8755,7 @@ export default { } ], "answerAgentChatQuestion": [ - 321, + 325, { "threadId": [ 4, @@ -8690,7 +8766,7 @@ export default { "UUID!" ], "answers": [ - 482, + 488, "[AgentChatQuestionAnswerInput!]!" ], "modelId": [ @@ -8708,7 +8784,7 @@ export default { } ], "renameChatThread": [ - 316, + 320, { "id": [ 4, @@ -8721,7 +8797,7 @@ export default { } ], "archiveChatThread": [ - 316, + 320, { "id": [ 4, @@ -8730,7 +8806,7 @@ export default { } ], "unarchiveChatThread": [ - 316, + 320, { "id": [ 4, @@ -8757,25 +8833,25 @@ export default { } ], "createSkill": [ - 314, + 318, { "input": [ - 483, + 489, "CreateSkillInput!" ] } ], "updateSkill": [ - 314, + 318, { "input": [ - 484, + 490, "UpdateSkillInput!" ] } ], "deleteSkill": [ - 314, + 318, { "id": [ 4, @@ -8784,7 +8860,7 @@ export default { } ], "activateSkill": [ - 314, + 318, { "id": [ 4, @@ -8793,7 +8869,7 @@ export default { } ], "deactivateSkill": [ - 314, + 318, { "id": [ 4, @@ -8802,7 +8878,7 @@ export default { } ], "evaluateAgentTurn": [ - 323, + 327, { "turnId": [ 4, @@ -8811,7 +8887,7 @@ export default { } ], "runEvaluationInput": [ - 324, + 328, { "agentId": [ 4, @@ -8827,7 +8903,7 @@ export default { 248, { "input": [ - 485, + 491, "GetAuthorizationUrlForSSOInput!" ] } @@ -8993,7 +9069,7 @@ export default { 251, { "input": [ - 486 + 492 ] } ], @@ -9005,7 +9081,7 @@ export default { "String!" ], "file": [ - 363, + 368, "Upload!" ] } @@ -9148,7 +9224,7 @@ export default { 3, { "input": [ - 487, + 493, "UpdateWorkspaceMemberSettingsInput!" ] } @@ -9182,7 +9258,7 @@ export default { 208, { "input": [ - 488, + 494, "SetupOIDCSsoInput!" ] } @@ -9191,7 +9267,7 @@ export default { 208, { "input": [ - 489, + 495, "SetupSAMLSsoInput!" ] } @@ -9200,7 +9276,7 @@ export default { 204, { "input": [ - 490, + 496, "DeleteSsoInput!" ] } @@ -9209,13 +9285,13 @@ export default { 205, { "input": [ - 491, + 497, "EditSsoInput!" ] } ], "createObjectEvent": [ - 310, + 314, { "event": [ 1, @@ -9235,10 +9311,10 @@ export default { } ], "trackAnalytics": [ - 310, + 314, { "type": [ - 492, + 498, "AnalyticsType!" ], "name": [ @@ -9253,7 +9329,7 @@ export default { } ], "duplicateDashboard": [ - 308, + 312, { "id": [ 4, @@ -9275,25 +9351,25 @@ export default { } ], "createCalendarEvent": [ - 300, + 304, { "input": [ - 493, + 499, "CreateCalendarEventInput!" ] } ], "sendEmail": [ - 309, + 313, { "input": [ - 494, + 500, "SendEmailInput!" ] } ], "startChannelSync": [ - 299, + 303, { "connectedAccountId": [ 4, @@ -9302,14 +9378,14 @@ export default { } ], "saveImapSmtpCaldavAccount": [ - 294, + 298, { "handle": [ 1, "String!" ], "connectionParameters": [ - 496, + 502, "EmailAccountConnectionParameters!" ], "id": [ @@ -9321,7 +9397,7 @@ export default { 168, { "input": [ - 498, + 504, "UpdateLabPublicFeatureFlagInput!" ] } @@ -9386,7 +9462,7 @@ export default { 268, { "file": [ - 363, + 368, "Upload!" ], "applicationUniversalIdentifier": [ @@ -9394,7 +9470,7 @@ export default { "String!" ], "fileFolder": [ - 362, + 367, "FileFolder!" ], "filePath": [ @@ -9499,7 +9575,7 @@ export default { 4 ], "update": [ - 361 + 366 ], "__typename": [ 1 @@ -9610,7 +9686,7 @@ export default { 4 ], "update": [ - 368 + 373 ], "__typename": [ 1 @@ -9781,19 +9857,19 @@ export default { 4 ], "view": [ - 374 + 379 ], "viewFields": [ - 375 + 380 ], "viewFilters": [ - 376 + 381 ], "viewFilterGroups": [ - 377 + 382 ], "viewSorts": [ - 378 + 383 ], "__typename": [ 1 @@ -9942,7 +10018,7 @@ export default { 4 ], "update": [ - 381 + 386 ], "__typename": [ 1 @@ -9980,7 +10056,7 @@ export default { 4 ], "update": [ - 385 + 390 ], "__typename": [ 1 @@ -10056,7 +10132,7 @@ export default { 4 ], "update": [ - 390 + 395 ], "__typename": [ 1 @@ -10120,10 +10196,10 @@ export default { 4 ], "groups": [ - 395 + 400 ], "fields": [ - 396 + 401 ], "__typename": [ 1 @@ -10143,7 +10219,7 @@ export default { 3 ], "fields": [ - 396 + 401 ], "__typename": [ 1 @@ -10311,7 +10387,7 @@ export default { 4 ], "tabs": [ - 408 + 413 ], "__typename": [ 1 @@ -10334,7 +10410,7 @@ export default { 85 ], "widgets": [ - 409 + 414 ], "__typename": [ 1 @@ -10357,7 +10433,7 @@ export default { 4 ], "gridPosition": [ - 410 + 415 ], "position": [ 5 @@ -10406,7 +10482,7 @@ export default { 4 ], "gridPosition": [ - 410 + 415 ], "position": [ 5 @@ -10432,7 +10508,7 @@ export default { 4 ], "gridPosition": [ - 410 + 415 ], "position": [ 5 @@ -10525,7 +10601,7 @@ export default { }, "CreateOneObjectInput": { "object": [ - 416 + 421 ], "__typename": [ 1 @@ -10585,7 +10661,7 @@ export default { }, "UpdateOneObjectInput": { "update": [ - 419 + 424 ], "id": [ 4 @@ -10640,7 +10716,7 @@ export default { }, "CreateOneIndexInput": { "index": [ - 421 + 426 ], "__typename": [ 1 @@ -10651,7 +10727,7 @@ export default { 4 ], "fields": [ - 422 + 427 ], "indexType": [ 27 @@ -10736,7 +10812,7 @@ export default { 4 ], "update": [ - 427 + 432 ], "__typename": [ 1 @@ -10896,7 +10972,7 @@ export default { 4 ], "update": [ - 432 + 437 ], "__typename": [ 1 @@ -11023,7 +11099,7 @@ export default { 1 ], "update": [ - 437 + 442 ], "__typename": [ 1 @@ -11077,7 +11153,7 @@ export default { 1 ], "update": [ - 440 + 445 ], "__typename": [ 1 @@ -11107,7 +11183,7 @@ export default { }, "CreateOneFieldMetadataInput": { "field": [ - 443 + 448 ], "__typename": [ 1 @@ -11180,7 +11256,7 @@ export default { 4 ], "update": [ - 445 + 450 ], "__typename": [ 1 @@ -11275,7 +11351,7 @@ export default { 4 ], "update": [ - 449 + 454 ], "__typename": [ 1 @@ -11360,7 +11436,7 @@ export default { }, "UpdateRoleInput": { "update": [ - 454 + 459 ], "id": [ 4 @@ -11415,7 +11491,7 @@ export default { 4 ], "objectPermissions": [ - 456 + 461 ], "__typename": [ 1 @@ -11457,7 +11533,7 @@ export default { 4 ], "fieldPermissions": [ - 459 + 464 ], "__typename": [ 1 @@ -11488,10 +11564,10 @@ export default { 4 ], "predicates": [ - 461 + 466 ], "predicateGroups": [ - 462 + 467 ], "__typename": [ 1 @@ -11609,7 +11685,7 @@ export default { 1 ], "visibility": [ - 287 + 291 ], "__typename": [ 1 @@ -11626,7 +11702,7 @@ export default { 1 ], "visibility": [ - 287 + 291 ], "__typename": [ 1 @@ -11637,7 +11713,7 @@ export default { 4 ], "update": [ - 468 + 473 ], "__typename": [ 1 @@ -11673,6 +11749,20 @@ export default { "handle": [ 1 ], + "displayName": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "UpdateEmailGroupChannelInput": { + "id": [ + 4 + ], + "displayName": [ + 1 + ], "__typename": [ 1 ] @@ -11721,7 +11811,7 @@ export default { 4 ], "update": [ - 474 + 480 ], "__typename": [ 1 @@ -11749,7 +11839,7 @@ export default { 4 ], "update": [ - 476 + 482 ], "__typename": [ 1 @@ -11768,7 +11858,7 @@ export default { 4 ], "update": [ - 476 + 482 ], "__typename": [ 1 @@ -11779,7 +11869,7 @@ export default { 4 ], "update": [ - 479 + 485 ], "__typename": [ 1 @@ -11787,13 +11877,13 @@ export default { }, "UpdateCalendarChannelInputUpdates": { "visibility": [ - 331 + 335 ], "isContactAutoCreationEnabled": [ 3 ], "contactAutoCreationPolicy": [ - 332 + 336 ], "isSyncEnabled": [ 3 @@ -11810,7 +11900,7 @@ export default { 5 ], "scope": [ - 327 + 331 ], "__typename": [ 1 @@ -12047,7 +12137,7 @@ export default { 1 ], "files": [ - 495 + 501 ], "__typename": [ 1 @@ -12069,13 +12159,13 @@ export default { 1 ], "IMAP": [ - 497 + 503 ], "SMTP": [ - 497 + 503 ], "CALDAV": [ - 497 + 503 ], "__typename": [ 1 @@ -12126,13 +12216,13 @@ export default { 236, { "input": [ - 500, + 506, "LogicFunctionLogsInput!" ] } ], "onAgentChatEvent": [ - 322, + 326, { "threadId": [ 4, @@ -12141,10 +12231,10 @@ export default { } ], "eventLogsLive": [ - 311, + 315, { "table": [ - 348, + 353, "EventLogTable!" ] } diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 4e2a8d6287..57a53965e3 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1137,6 +1137,7 @@ export type CreateCommandMenuItemInput = { }; export type CreateEmailGroupChannelInput = { + displayName?: InputMaybe; handle: Scalars['String']['input']; }; @@ -1971,6 +1972,14 @@ export type FindAvailableSsoidp = { workspace: WorkspaceNameAndId; }; +export type FindMessageSuppressionsInput = { + limit?: Scalars['Int']['input']; + offset?: Scalars['Int']['input']; + reason?: InputMaybe; + searchTerm?: InputMaybe; + unsubscribeTopicId?: InputMaybe; +}; + export type FrontComponent = { __typename?: 'FrontComponent'; applicationId: Scalars['UUID']['output']; @@ -2369,6 +2378,7 @@ export type MessageChannel = { connectedAccountId: Scalars['UUID']['output']; contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy; createdAt: Scalars['DateTime']['output']; + displayName?: Maybe; excludeGroupEmails: Scalars['Boolean']['output']; excludeNonProfessionalEmails: Scalars['Boolean']['output']; handle: Scalars['String']['output']; @@ -2456,6 +2466,33 @@ export enum MessageFolderPendingSyncAction { NONE = 'NONE' } +export type MessageSuppression = { + __typename?: 'MessageSuppression'; + createdAt: Scalars['DateTime']['output']; + emailAddress: Scalars['String']['output']; + id: Scalars['UUID']['output']; + reason: MessageSuppressionReason; + source: MessageSuppressionSource; + unsubscribeTopicId?: Maybe; +}; + +export type MessageSuppressionList = { + __typename?: 'MessageSuppressionList'; + records: Array; + totalCount: Scalars['Int']['output']; +}; + +export enum MessageSuppressionReason { + BOUNCE = 'BOUNCE', + COMPLAINT = 'COMPLAINT', + UNSUBSCRIBE = 'UNSUBSCRIBE' +} + +export enum MessageSuppressionSource { + SYSTEM = 'SYSTEM', + WEBHOOK = 'WEBHOOK' +} + export type MetadataEvent = { __typename?: 'MetadataEvent'; metadataName: Scalars['String']['output']; @@ -2685,6 +2722,7 @@ export type Mutation = { updateApplicationRegistrationVariable: ApplicationRegistrationVariable; updateCalendarChannel: CalendarChannel; updateCommandMenuItem: CommandMenuItem; + updateEmailGroupChannel: MessageChannel; updateFrontComponent: FrontComponent; updateLabPublicFeatureFlag: FeatureFlag; updateManyNavigationMenuItems: Array; @@ -3592,6 +3630,11 @@ export type MutationUpdateCommandMenuItemArgs = { }; +export type MutationUpdateEmailGroupChannelArgs = { + input: UpdateEmailGroupChannelInput; +}; + + export type MutationUpdateFrontComponentArgs = { input: UpdateFrontComponentInput; }; @@ -4457,6 +4500,7 @@ export type Query = { isApplicationStopped: Scalars['Boolean']['output']; lineChartData: LineChartData; listPlans: Array; + messageSuppressions: MessageSuppressionList; minimalMetadata: MinimalMetadata; mostlyEmptyFieldMetadataIds: Array; myCalendarChannels: Array; @@ -4474,7 +4518,6 @@ export type Query = { publicMarketplaceApps: Array; skill?: Maybe; skills: Array; - unsubscribePagePreviewUrl: Scalars['String']['output']; unsubscribeTopics: Array; validatePasswordResetToken: ValidatePasswordResetToken; webhook?: Maybe; @@ -4829,6 +4872,11 @@ export type QueryLineChartDataArgs = { }; +export type QueryMessageSuppressionsArgs = { + input: FindMessageSuppressionsInput; +}; + + export type QueryMostlyEmptyFieldMetadataIdsArgs = { objectMetadataId: Scalars['UUID']['input']; }; @@ -5459,6 +5507,11 @@ export type UpdateCommandMenuItemInput = { shortLabel?: InputMaybe; }; +export type UpdateEmailGroupChannelInput = { + displayName?: InputMaybe; + id: Scalars['UUID']['input']; +}; + export type UpdateFieldInput = { defaultValue?: InputMaybe; description?: InputMaybe; @@ -7817,6 +7870,13 @@ export type UpdateCalendarChannelMutationVariables = Exact<{ export type UpdateCalendarChannelMutation = { __typename?: 'Mutation', updateCalendarChannel: { __typename?: 'CalendarChannel', id: string, visibility: CalendarChannelVisibility, isContactAutoCreationEnabled: boolean, contactAutoCreationPolicy: CalendarChannelContactAutoCreationPolicy } }; +export type UpdateEmailGroupChannelMutationVariables = Exact<{ + input: UpdateEmailGroupChannelInput; +}>; + + +export type UpdateEmailGroupChannelMutation = { __typename?: 'Mutation', updateEmailGroupChannel: { __typename?: 'MessageChannel', id: string, displayName?: string | null } }; + export type UpdateMessageChannelMutationVariables = Exact<{ input: UpdateMessageChannelInput; }>; @@ -7862,7 +7922,7 @@ export type MyMessageChannelsQueryVariables = Exact<{ }>; -export type MyMessageChannelsQuery = { __typename?: 'Query', myMessageChannels: Array<{ __typename?: 'MessageChannel', id: string, handle: string, visibility: MessageChannelVisibility, type: MessageChannelType, isContactAutoCreationEnabled: boolean, contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy, messageFolderImportPolicy: MessageFolderImportPolicy, excludeNonProfessionalEmails: boolean, excludeGroupEmails: boolean, isSyncEnabled: boolean, syncStatus: MessageChannelSyncStatus, syncStage: MessageChannelSyncStage, syncStageStartedAt?: string | null, connectedAccountId: string, createdAt: string, updatedAt: string, connectedAccount?: { __typename?: 'ConnectedAccountPublicDTO', id: string, handle: string } | null }> }; +export type MyMessageChannelsQuery = { __typename?: 'Query', myMessageChannels: Array<{ __typename?: 'MessageChannel', id: string, handle: string, displayName?: string | null, visibility: MessageChannelVisibility, type: MessageChannelType, isContactAutoCreationEnabled: boolean, contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy, messageFolderImportPolicy: MessageFolderImportPolicy, excludeNonProfessionalEmails: boolean, excludeGroupEmails: boolean, isSyncEnabled: boolean, syncStatus: MessageChannelSyncStatus, syncStage: MessageChannelSyncStage, syncStageStartedAt?: string | null, connectedAccountId: string, createdAt: string, updatedAt: string, connectedAccount?: { __typename?: 'ConnectedAccountPublicDTO', id: string, handle: string } | null }> }; export type MyMessageFoldersQueryVariables = Exact<{ messageChannelId?: InputMaybe; @@ -8473,10 +8533,12 @@ export type UpdateUnsubscribeTopicMutationVariables = Exact<{ export type UpdateUnsubscribeTopicMutation = { __typename?: 'Mutation', updateUnsubscribeTopic: { __typename?: 'UnsubscribeTopic', id: string, name?: string | null, description?: string | null, visibility: UnsubscribeTopicVisibility } }; -export type UnsubscribePagePreviewUrlQueryVariables = Exact<{ [key: string]: never; }>; +export type MessageSuppressionsQueryVariables = Exact<{ + input: FindMessageSuppressionsInput; +}>; -export type UnsubscribePagePreviewUrlQuery = { __typename?: 'Query', unsubscribePagePreviewUrl: string }; +export type MessageSuppressionsQuery = { __typename?: 'Query', messageSuppressions: { __typename?: 'MessageSuppressionList', totalCount: number, records: Array<{ __typename?: 'MessageSuppression', id: string, createdAt: string, emailAddress: string, reason: MessageSuppressionReason, unsubscribeTopicId?: string | null }> } }; export type GetUsageAnalyticsQueryVariables = Exact<{ input?: InputMaybe; @@ -9106,13 +9168,14 @@ export const DeleteEmailGroupChannelDocument = {"kind":"Document","definitions": export const SaveImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SaveImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"handle"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectionParameters"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"EmailAccountConnectionParameters"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"saveImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"handle"},"value":{"kind":"Variable","name":{"kind":"Name","value":"handle"}}},{"kind":"Argument","name":{"kind":"Name","value":"connectionParameters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectionParameters"}}},{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}}]}}]}}]} as unknown as DocumentNode; export const StartChannelSyncDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StartChannelSync"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startChannelSync"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const UpdateCalendarChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCalendarChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateCalendarChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCalendarChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}}]}}]}}]} as unknown as DocumentNode; +export const UpdateEmailGroupChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateEmailGroupChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateEmailGroupChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateEmailGroupChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]} as unknown as DocumentNode; export const UpdateMessageChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}}]}}]}}]} as unknown as DocumentNode; export const UpdateMessageFolderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageFolder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageFolderInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageFolder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}}]}}]}}]} as unknown as DocumentNode; export const UpdateMessageFoldersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageFolders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageFoldersInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageFolders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}},{"kind":"Field","name":{"kind":"Name","value":"pendingSyncAction"}}]}}]}}]} as unknown as DocumentNode; export const GetConnectedImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConnectedImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConnectedImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"connectionSecurity"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"connectionSecurity"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"connectionSecurity"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const MyCalendarChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyCalendarChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myCalendarChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const MyConnectedAccountsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"authFailedAt"}},{"kind":"Field","name":{"kind":"Name","value":"archivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"handleAliases"}},{"kind":"Field","name":{"kind":"Name","value":"lastSignedInAt"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionProviderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"lastCredentialsRefreshedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"connectionSecurity"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"connectionSecurity"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; -export const MyMessageChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyMessageChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myMessageChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; +export const MyMessageChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyMessageChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myMessageChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const MyMessageFoldersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyMessageFolders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"messageChannelId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myMessageFolders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"messageChannelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"messageChannelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}},{"kind":"Field","name":{"kind":"Name","value":"isSentFolder"}},{"kind":"Field","name":{"kind":"Name","value":"parentFolderId"}},{"kind":"Field","name":{"kind":"Name","value":"externalId"}},{"kind":"Field","name":{"kind":"Name","value":"pendingSyncAction"}},{"kind":"Field","name":{"kind":"Name","value":"messageChannelId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const ClaimApplicationRegistrationOwnershipDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ClaimApplicationRegistrationOwnership"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"claimApplicationRegistrationOwnership"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const DeleteApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode; @@ -9197,7 +9260,7 @@ export const VerifyTwoFactorAuthenticationMethodForAuthenticatedUserDocument = { export const CreateUnsubscribeTopicDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateUnsubscribeTopic"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateUnsubscribeTopicInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createUnsubscribeTopic"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}}]}}]}}]} as unknown as DocumentNode; export const DeleteUnsubscribeTopicDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteUnsubscribeTopic"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteUnsubscribeTopic"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode; export const UpdateUnsubscribeTopicDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUnsubscribeTopic"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateUnsubscribeTopicInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUnsubscribeTopic"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}}]}}]}}]} as unknown as DocumentNode; -export const UnsubscribePagePreviewUrlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UnsubscribePagePreviewUrl"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unsubscribePagePreviewUrl"}}]}}]} as unknown as DocumentNode; +export const MessageSuppressionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MessageSuppressions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"FindMessageSuppressionsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messageSuppressions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"records"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"emailAddress"}},{"kind":"Field","name":{"kind":"Name","value":"reason"}},{"kind":"Field","name":{"kind":"Name","value":"unsubscribeTopicId"}}]}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode; export const GetUsageAnalyticsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUsageAnalytics"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UsageAnalyticsInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getUsageAnalytics"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"usageByUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"creditsUsed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"usageByOperationType"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"creditsUsed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"usageByModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"creditsUsed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"timeSeries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"creditsUsed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"periodStart"}},{"kind":"Field","name":{"kind":"Name","value":"periodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"userDailyUsage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"dailyUsage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"date"}},{"kind":"Field","name":{"kind":"Name","value":"creditsUsed"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteUserAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteUserAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const DeleteUserWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteUserWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceMemberIdToDelete"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteUserFromWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceMemberIdToDelete"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceMemberIdToDelete"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/accounts/types/MessageChannel.ts b/packages/twenty-front/src/modules/accounts/types/MessageChannel.ts index fa99cacb86..32227dfe17 100644 --- a/packages/twenty-front/src/modules/accounts/types/MessageChannel.ts +++ b/packages/twenty-front/src/modules/accounts/types/MessageChannel.ts @@ -10,6 +10,7 @@ import { type MessageChannelVisibility } from '~/generated/graphql'; export type MessageChannel = { id: string; handle: string; + displayName: string | null; visibility: MessageChannelVisibility; type: MessageChannelType; isContactAutoCreationEnabled: boolean; diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 9fa0d10da8..4b9140c4b6 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -214,6 +214,14 @@ const SettingsWorkspaceUnsubscribeTopicDetail = lazy(() => ), ); +const SettingsWorkspaceUnsubscribe = lazy(() => + import('~/pages/settings/communications/SettingsWorkspaceUnsubscribe').then( + (module) => ({ + default: module.SettingsWorkspaceUnsubscribe, + }), + ), +); + const SettingsSubdomainPage = lazy(() => import('~/pages/settings/domains/SettingsSubdomainPage').then((module) => ({ default: module.SettingsSubdomainPage, @@ -724,6 +732,10 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.UnsubscribeTopicDetail} element={} /> + } + /> } /> { const { createEmailGroupChannel, loading } = useCreateEmailGroupChannel(); const [handle, setHandle] = useState(''); + const [displayName, setDisplayName] = useState(''); const isHandleValidEmail = z.email().safeParse(handle).success; const canSave = isHandleValidEmail && !loading; const handleSave = useCallback(async () => { - const result = await createEmailGroupChannel(handle); + const trimmedDisplayName = displayName.trim(); + const result = await createEmailGroupChannel( + handle, + trimmedDisplayName.length > 0 ? trimmedDisplayName : undefined, + ); const messageChannelId = result.data?.createEmailGroupChannel.messageChannel.id; @@ -34,7 +39,7 @@ export const SettingsAccountsNewEmailGroupChannel = () => { messageChannelId, }); } - }, [createEmailGroupChannel, handle, navigate]); + }, [createEmailGroupChannel, displayName, handle, navigate]); return ( { disabled={loading} /> + + + { + if (canSave) { + handleSave(); + } + }} + disabled={loading} + /> + ); diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/updateEmailGroupChannel.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/updateEmailGroupChannel.ts new file mode 100644 index 0000000000..e73a71bca0 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/accounts/graphql/mutations/updateEmailGroupChannel.ts @@ -0,0 +1,10 @@ +import { gql } from '@apollo/client'; + +export const UPDATE_EMAIL_GROUP_CHANNEL = gql` + mutation UpdateEmailGroupChannel($input: UpdateEmailGroupChannelInput!) { + updateEmailGroupChannel(input: $input) { + id + displayName + } + } +`; diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyMessageChannels.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyMessageChannels.ts index bb1a377cb4..bb9de2bfbd 100644 --- a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyMessageChannels.ts +++ b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getMyMessageChannels.ts @@ -5,6 +5,7 @@ export const GET_MY_MESSAGE_CHANNELS = gql` myMessageChannels(connectedAccountId: $connectedAccountId) { id handle + displayName visibility type isContactAutoCreationEnabled diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useCreateEmailGroupChannel.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useCreateEmailGroupChannel.ts index 12fb62f4aa..a8d97ec4f4 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useCreateEmailGroupChannel.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useCreateEmailGroupChannel.ts @@ -30,6 +30,7 @@ type CreateEmailGroupChannelResult = { type CreateEmailGroupChannelVariables = { input: { handle: string; + displayName?: string; }; }; @@ -47,9 +48,9 @@ export const useCreateEmailGroupChannel = () => { ], }); - const createEmailGroupChannel = (handle: string) => + const createEmailGroupChannel = (handle: string, displayName?: string) => mutate({ - variables: { input: { handle } }, + variables: { input: { handle, displayName } }, onError: (mutationError) => { enqueueErrorSnackBar({ apolloError: mutationError }); }, diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateEmailGroupChannel.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateEmailGroupChannel.ts new file mode 100644 index 0000000000..ba7272b318 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateEmailGroupChannel.ts @@ -0,0 +1,32 @@ +import { useMutation } from '@apollo/client/react'; + +import { UPDATE_EMAIL_GROUP_CHANNEL } from '@/settings/accounts/graphql/mutations/updateEmailGroupChannel'; +import { GET_MY_MESSAGE_CHANNELS } from '@/settings/accounts/graphql/queries/getMyMessageChannels'; + +type UpdateEmailGroupChannelResult = { + updateEmailGroupChannel: { + id: string; + displayName: string | null; + }; +}; + +type UpdateEmailGroupChannelVariables = { + input: { + id: string; + displayName?: string | null; + }; +}; + +export const useUpdateEmailGroupChannel = () => { + const [mutate, { loading, error }] = useMutation< + UpdateEmailGroupChannelResult, + UpdateEmailGroupChannelVariables + >(UPDATE_EMAIL_GROUP_CHANNEL, { + refetchQueries: [{ query: GET_MY_MESSAGE_CHANNELS }], + }); + + const updateEmailGroupChannel = (id: string, displayName: string | null) => + mutate({ variables: { input: { id, displayName } } }); + + return { updateEmailGroupChannel, loading, error }; +}; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsPaginationControls.tsx b/packages/twenty-front/src/modules/settings/components/SettingsPaginationControls.tsx new file mode 100644 index 0000000000..04adb3f61c --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/SettingsPaginationControls.tsx @@ -0,0 +1,55 @@ +import { styled } from '@linaria/react'; +import { useLingui } from '@lingui/react/macro'; +import { isDefined } from 'twenty-shared/utils'; +import { Button } from 'twenty-ui/input'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +type SettingsPaginationControlsProps = { + page: number; + pageCount: number; + onPageChange: (page: number) => void; + isLoading?: boolean; + hasNextPage?: boolean; +}; + +const StyledContainer = styled.div` + align-items: center; + display: flex; + gap: ${themeCssVariables.spacing[2]}; + justify-content: flex-end; + padding-top: ${themeCssVariables.spacing[2]}; +`; + +export const SettingsPaginationControls = ({ + page, + pageCount, + onPageChange, + isLoading = false, + hasNextPage, +}: SettingsPaginationControlsProps) => { + const { t } = useLingui(); + + const isNextDisabled = isDefined(hasNextPage) + ? !hasNextPage + : page + 1 >= pageCount; + + return ( + + onPageChange(page - 1)} + /> + {t`Page ${page + 1} of ${pageCount}`} + onPageChange(page + 1)} + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx b/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx index 3eee717fdd..3fd3c310d9 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx @@ -1,5 +1,7 @@ import { type ComponentType, type ReactNode } from 'react'; +import { isDefined } from 'twenty-shared/utils'; + import { styled } from '@linaria/react'; import { Table } from '@/ui/layout/table/components/Table'; @@ -50,13 +52,14 @@ type SettingsTableListSectionProps = { title: string; description: string; headerAdornment?: ReactNode; + toolbar?: ReactNode; items: Item[]; columns: SettingsTableListSectionColumn[]; gridAutoColumns: string; showRowChevron?: boolean; onRowClick?: (item: Item) => void; - footerButtonLabel: string; - onFooterButtonClick: () => void; + footerButtonLabel?: string; + onFooterButtonClick?: () => void; }; export const SettingsTableListSection = < @@ -65,6 +68,7 @@ export const SettingsTableListSection = < title, description, headerAdornment, + toolbar, items, columns, gridAutoColumns, @@ -84,6 +88,7 @@ export const SettingsTableListSection = < description={description} adornment={headerAdornment} /> + {isDefined(toolbar) && toolbar} {items.length > 0 && ( @@ -124,15 +129,17 @@ export const SettingsTableListSection = < )} - - - + {isDefined(footerButtonLabel) && isDefined(onFooterButtonClick) && ( + + + + )} ); }; diff --git a/packages/twenty-front/src/modules/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl.ts b/packages/twenty-front/src/modules/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl.ts deleted file mode 100644 index 84cc46948d..0000000000 --- a/packages/twenty-front/src/modules/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_UNSUBSCRIBE_PAGE_PREVIEW_URL = gql` - query UnsubscribePagePreviewUrl { - unsubscribePagePreviewUrl - } -`; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribePreview.tsx b/packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribePreview.tsx new file mode 100644 index 0000000000..c1c8c29bdb --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribePreview.tsx @@ -0,0 +1,107 @@ +import { styled } from '@linaria/react'; +import { useLingui } from '@lingui/react/macro'; + +import { useUnsubscribeTopics } from '@/activities/emails/hooks/useUnsubscribeTopics'; +import { Button, Checkbox } from 'twenty-ui/input'; +import { HorizontalSeparator, Section } from 'twenty-ui/layout'; +import { Card } from 'twenty-ui/surfaces'; +import { H2Title } from 'twenty-ui/typography'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { UnsubscribeTopicVisibility } from '~/generated-metadata/graphql'; + +const StyledViewport = styled.div` + align-items: center; + background: ${themeCssVariables.background.secondary}; + border: 1px solid ${themeCssVariables.border.color.medium}; + border-radius: ${themeCssVariables.border.radius.md}; + display: flex; + justify-content: center; + padding: ${themeCssVariables.spacing[10]} ${themeCssVariables.spacing[6]}; +`; + +const StyledCard = styled(Card)` + --card-background-color: ${themeCssVariables.background.primary}; + + display: flex; + flex-direction: column; + gap: ${themeCssVariables.spacing[5]}; + max-width: 420px; + padding: ${themeCssVariables.spacing[8]}; + width: 100%; +`; + +const StyledHeader = styled.div` + text-align: center; + + h2 { + font-size: 20px; + } +`; + +const StyledTopics = styled.div` + display: flex; + flex-direction: column; + gap: ${themeCssVariables.spacing[3]}; +`; + +const StyledTopicRow = styled.div` + align-items: center; + color: ${themeCssVariables.font.color.primary}; + display: flex; + font-size: ${themeCssVariables.font.size.md}; + gap: ${themeCssVariables.spacing[2]}; +`; + +export const SettingsUnsubscribePreview = () => { + const { t } = useLingui(); + const { unsubscribeTopics } = useUnsubscribeTopics(); + + const publicTopics = unsubscribeTopics.filter( + (topic) => topic.visibility === UnsubscribeTopicVisibility.PUBLIC, + ); + + return ( + + + + + + + + + {publicTopics.map((topic) => ( + + {}} + aria-label={topic.name ?? t`Untitled topic`} + /> + {topic.name ?? t`Untitled topic`} + + ))} + + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribersList.tsx b/packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribersList.tsx new file mode 100644 index 0000000000..3617fc8df9 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/SettingsUnsubscribersList.tsx @@ -0,0 +1,196 @@ +import { styled } from '@linaria/react'; +import { useLingui } from '@lingui/react/macro'; +import { useState } from 'react'; +import { useDebounce } from 'use-debounce'; +import { isDefined } from 'twenty-shared/utils'; + +import { useUnsubscribeTopics } from '@/activities/emails/hooks/useUnsubscribeTopics'; +import { SettingsEmptyPlaceholder } from '@/settings/components/SettingsEmptyPlaceholder'; +import { SettingsPaginationControls } from '@/settings/components/SettingsPaginationControls'; +import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader'; +import { SettingsTableListSection } from '@/settings/components/SettingsTableListSection'; +import { SettingsUnsubscribersFilterDropdown } from '@/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterDropdown'; +import { SETTINGS_UNSUBSCRIBERS_ALL_FILTER } from '@/settings/unsubscribers/constants/SettingsUnsubscribersAllFilter'; +import { MESSAGE_SUPPRESSIONS_PAGE_SIZE } from '@/settings/unsubscribers/constants/MessageSuppressionsPageSize'; +import { useMessageSuppressions } from '@/settings/unsubscribers/hooks/useMessageSuppressions'; +import { getMessageSuppressionReasonBadge } from '@/settings/unsubscribers/utils/getMessageSuppressionReasonBadge'; +import { Status } from 'twenty-ui/data-display'; +import { SearchInput } from 'twenty-ui/input'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { + MessageSuppressionReason, + type MessageSuppressionsQuery, +} from '~/generated-metadata/graphql'; +import { formatToHumanReadableDate } from '~/utils/date-utils'; + +type MessageSuppression = + MessageSuppressionsQuery['messageSuppressions']['records'][number]; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + gap: ${themeCssVariables.spacing[4]}; +`; + +const StyledToolbar = styled.div` + display: flex; + gap: ${themeCssVariables.spacing[2]}; + padding-bottom: ${themeCssVariables.spacing[2]}; + width: 100%; +`; + +const StyledSearch = styled.div` + flex: 1; +`; + +export const SettingsUnsubscribersList = () => { + const { t } = useLingui(); + + const [page, setPage] = useState(0); + const [searchText, setSearchText] = useState(''); + const [debouncedSearchText] = useDebounce(searchText, 300); + const [reasonFilter, setReasonFilter] = useState( + SETTINGS_UNSUBSCRIBERS_ALL_FILTER, + ); + const [topicFilter, setTopicFilter] = useState( + SETTINGS_UNSUBSCRIBERS_ALL_FILTER, + ); + + const { unsubscribeTopics } = useUnsubscribeTopics(); + + const reason = Object.values(MessageSuppressionReason).find( + (reasonValue) => reasonValue === reasonFilter, + ); + const unsubscribeTopicId = + topicFilter === SETTINGS_UNSUBSCRIBERS_ALL_FILTER ? undefined : topicFilter; + + const { messageSuppressions, totalCount, loading } = useMessageSuppressions({ + page, + searchTerm: debouncedSearchText, + reason, + unsubscribeTopicId, + }); + + const goToFirstPage = () => setPage(0); + + const handleSearchChange = (value: string) => { + setSearchText(value); + goToFirstPage(); + }; + + const handleReasonChange = (value: string) => { + setReasonFilter(value); + goToFirstPage(); + }; + + const handleTopicChange = (value: string) => { + setTopicFilter(value); + goToFirstPage(); + }; + + const handleClearFilters = () => { + setReasonFilter(SETTINGS_UNSUBSCRIBERS_ALL_FILTER); + setTopicFilter(SETTINGS_UNSUBSCRIBERS_ALL_FILTER); + goToFirstPage(); + }; + + const topicNameById = new Map( + unsubscribeTopics.map((topic) => [ + topic.id, + topic.name ?? t`Untitled topic`, + ]), + ); + + const getScopeLabel = (topicId: string | null) => { + if (!isDefined(topicId)) { + return t`All emails`; + } + + return topicNameById.get(topicId) ?? t`Unknown topic`; + }; + + const items = loading ? [] : messageSuppressions; + const isEmpty = !loading && items.length === 0; + const hasPagination = totalCount > MESSAGE_SUPPRESSIONS_PAGE_SIZE; + const pageCount = Math.max( + 1, + Math.ceil(totalCount / MESSAGE_SUPPRESSIONS_PAGE_SIZE), + ); + + return ( + + + title={t`Unsubscribers`} + description={t`Email addresses that will no longer receive campaign emails`} + toolbar={ + + + + + + + } + items={items} + columns={[ + { + label: t`Email address`, + Cell: ({ item }) => <>{item.emailAddress}>, + }, + { + label: t`Scope`, + Cell: ({ item }) => ( + <>{getScopeLabel(item.unsubscribeTopicId ?? null)}> + ), + }, + { + label: t`Reason`, + Cell: ({ item }) => { + const badge = getMessageSuppressionReasonBadge(item.reason); + + return ( + + ); + }, + }, + { + label: t`Date`, + align: 'right', + Cell: ({ item }) => ( + <>{formatToHumanReadableDate(item.createdAt)}> + ), + }, + ]} + gridAutoColumns="1fr 160px 140px 160px" + /> + {loading && } + {isEmpty && ( + + {t`No unsubscribers found`} + + )} + {hasPagination && ( + + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterDropdown.tsx b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterDropdown.tsx new file mode 100644 index 0000000000..c985bca6bc --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterDropdown.tsx @@ -0,0 +1,123 @@ +import { useLingui } from '@lingui/react/macro'; +import { useState } from 'react'; +import { IconFilter } from 'twenty-ui/icon'; +import { Button } from 'twenty-ui/input'; + +import { SettingsUnsubscribersFilterMenuContent } from '@/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterMenuContent'; +import { SettingsUnsubscribersFilterOptionsContent } from '@/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterOptionsContent'; +import { type SettingsUnsubscribersFilterContentId } from '@/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterContentId'; +import { type SettingsUnsubscribersFilterOption } from '@/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterOption'; +import { SETTINGS_UNSUBSCRIBERS_ALL_FILTER } from '@/settings/unsubscribers/constants/SettingsUnsubscribersAllFilter'; +import { getMessageSuppressionReasonBadge } from '@/settings/unsubscribers/utils/getMessageSuppressionReasonBadge'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { + MessageSuppressionReason, + type UnsubscribeTopicsQuery, +} from '~/generated-metadata/graphql'; + +const SETTINGS_UNSUBSCRIBERS_FILTER_DROPDOWN_ID = + 'settings-unsubscribers-filter-dropdown'; + +const getOptionLabel = ( + options: SettingsUnsubscribersFilterOption[], + value: string, +) => options.find((option) => option.value === value)?.label ?? ''; + +type SettingsUnsubscribersFilterDropdownProps = { + topics: UnsubscribeTopicsQuery['unsubscribeTopics']; + reasonValue: string; + topicValue: string; + onChangeReason: (value: string) => void; + onChangeTopic: (value: string) => void; + onClear: () => void; +}; + +export const SettingsUnsubscribersFilterDropdown = ({ + topics, + reasonValue, + topicValue, + onChangeReason, + onChangeTopic, + onClear, +}: SettingsUnsubscribersFilterDropdownProps) => { + const { t } = useLingui(); + + const [contentId, setContentId] = + useState(null); + + const goToMenu = () => setContentId(null); + + const reasonOptions: SettingsUnsubscribersFilterOption[] = [ + { value: SETTINGS_UNSUBSCRIBERS_ALL_FILTER, label: t`All reasons` }, + ...Object.values(MessageSuppressionReason).map((reason) => ({ + value: reason, + label: getMessageSuppressionReasonBadge(reason).label, + })), + ]; + + const topicOptions: SettingsUnsubscribersFilterOption[] = [ + { value: SETTINGS_UNSUBSCRIBERS_ALL_FILTER, label: t`All topics` }, + ...topics.map((topic) => ({ + value: topic.id, + label: topic.name ?? t`Untitled topic`, + })), + ]; + + const hasActiveFilters = + reasonValue !== SETTINGS_UNSUBSCRIBERS_ALL_FILTER || + topicValue !== SETTINGS_UNSUBSCRIBERS_ALL_FILTER; + + const renderContent = () => { + switch (contentId) { + case 'reason': + return ( + + ); + case 'topic': + return ( + + ); + default: + return ( + + ); + } + }; + + return ( + + } + dropdownComponents={renderContent()} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterMenuContent.tsx b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterMenuContent.tsx new file mode 100644 index 0000000000..dbd4bcd93b --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterMenuContent.tsx @@ -0,0 +1,60 @@ +import { useLingui } from '@lingui/react/macro'; +import { IconMailCog, IconStatusChange, IconTrash } from 'twenty-ui/icon'; +import { MenuItem } from 'twenty-ui/navigation'; + +import { type SettingsUnsubscribersFilterContentId } from '@/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterContentId'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; + +type SettingsUnsubscribersFilterMenuContentProps = { + reasonLabel: string; + topicLabel: string; + hasActiveFilters: boolean; + onContentChange: (contentId: SettingsUnsubscribersFilterContentId) => void; + onClear: () => void; +}; + +export const SettingsUnsubscribersFilterMenuContent = ({ + reasonLabel, + topicLabel, + hasActiveFilters, + onContentChange, + onClear, +}: SettingsUnsubscribersFilterMenuContentProps) => { + const { t } = useLingui(); + + return ( + + + onContentChange('reason')} + /> + onContentChange('topic')} + /> + {hasActiveFilters && ( + <> + + + > + )} + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterOptionsContent.tsx b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterOptionsContent.tsx new file mode 100644 index 0000000000..167be09c7b --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/SettingsUnsubscribersFilterOptionsContent.tsx @@ -0,0 +1,57 @@ +import { IconChevronLeft } from 'twenty-ui/icon'; +import { MenuItemSelect } from 'twenty-ui/navigation'; + +import { type SettingsUnsubscribersFilterOption } from '@/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterOption'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; +import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; + +type SettingsUnsubscribersFilterOptionsContentProps = { + title: string; + options: SettingsUnsubscribersFilterOption[]; + selectedValue: string; + onSelect: (value: string) => void; + onBack: () => void; +}; + +export const SettingsUnsubscribersFilterOptionsContent = ({ + title, + options, + selectedValue, + onSelect, + onBack, +}: SettingsUnsubscribersFilterOptionsContentProps) => { + const { closeDropdown } = useCloseDropdown(); + + const handleSelect = (value: string) => { + onSelect(value); + closeDropdown(); + }; + + return ( + + + } + > + {title} + + + {options.map((option) => ( + handleSelect(option.value)} + /> + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterContentId.ts b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterContentId.ts new file mode 100644 index 0000000000..50c4ef2fbe --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterContentId.ts @@ -0,0 +1 @@ +export type SettingsUnsubscribersFilterContentId = 'reason' | 'topic'; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterOption.ts b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterOption.ts new file mode 100644 index 0000000000..73ea5aa3cb --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/components/filter-dropdown/types/SettingsUnsubscribersFilterOption.ts @@ -0,0 +1,4 @@ +export type SettingsUnsubscribersFilterOption = { + value: string; + label: string; +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/constants/MessageSuppressionsPageSize.ts b/packages/twenty-front/src/modules/settings/unsubscribers/constants/MessageSuppressionsPageSize.ts new file mode 100644 index 0000000000..edf2483654 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/constants/MessageSuppressionsPageSize.ts @@ -0,0 +1 @@ +export const MESSAGE_SUPPRESSIONS_PAGE_SIZE = 30; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribeTabIds.ts b/packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribeTabIds.ts new file mode 100644 index 0000000000..39f3849077 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribeTabIds.ts @@ -0,0 +1,5 @@ +export const SETTINGS_UNSUBSCRIBE_TAB_IDS = { + UNSUBSCRIBERS: 'unsubscribers', + TOPICS: 'topics', + PREVIEW: 'preview', +} as const; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribersAllFilter.ts b/packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribersAllFilter.ts new file mode 100644 index 0000000000..ca20115034 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/constants/SettingsUnsubscribersAllFilter.ts @@ -0,0 +1 @@ +export const SETTINGS_UNSUBSCRIBERS_ALL_FILTER = 'all'; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/graphql/queries/getMessageSuppressions.ts b/packages/twenty-front/src/modules/settings/unsubscribers/graphql/queries/getMessageSuppressions.ts new file mode 100644 index 0000000000..91b0fade45 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/graphql/queries/getMessageSuppressions.ts @@ -0,0 +1,16 @@ +import { gql } from '@apollo/client'; + +export const MESSAGE_SUPPRESSIONS = gql` + query MessageSuppressions($input: FindMessageSuppressionsInput!) { + messageSuppressions(input: $input) { + records { + id + createdAt + emailAddress + reason + unsubscribeTopicId + } + totalCount + } + } +`; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/hooks/useMessageSuppressions.ts b/packages/twenty-front/src/modules/settings/unsubscribers/hooks/useMessageSuppressions.ts new file mode 100644 index 0000000000..284bcd2947 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/hooks/useMessageSuppressions.ts @@ -0,0 +1,43 @@ +import { useQuery } from '@apollo/client/react'; +import { isNonEmptyString } from '@sniptt/guards'; + +import { MESSAGE_SUPPRESSIONS_PAGE_SIZE } from '@/settings/unsubscribers/constants/MessageSuppressionsPageSize'; +import { + type MessageSuppressionReason, + MessageSuppressionsDocument, +} from '~/generated-metadata/graphql'; + +type UseMessageSuppressionsParams = { + page?: number; + searchTerm?: string; + reason?: MessageSuppressionReason; + unsubscribeTopicId?: string; + skip?: boolean; +}; + +export const useMessageSuppressions = ({ + page = 0, + searchTerm, + reason, + unsubscribeTopicId, + skip, +}: UseMessageSuppressionsParams) => { + const { data, loading } = useQuery(MessageSuppressionsDocument, { + variables: { + input: { + limit: MESSAGE_SUPPRESSIONS_PAGE_SIZE, + offset: page * MESSAGE_SUPPRESSIONS_PAGE_SIZE, + ...(isNonEmptyString(searchTerm) ? { searchTerm } : {}), + ...(isNonEmptyString(unsubscribeTopicId) ? { unsubscribeTopicId } : {}), + ...(reason ? { reason } : {}), + }, + }, + skip, + }); + + return { + messageSuppressions: data?.messageSuppressions.records ?? [], + totalCount: data?.messageSuppressions.totalCount ?? 0, + loading, + }; +}; diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/utils/__tests__/getMessageSuppressionReasonBadge.test.ts b/packages/twenty-front/src/modules/settings/unsubscribers/utils/__tests__/getMessageSuppressionReasonBadge.test.ts new file mode 100644 index 0000000000..e1239142f5 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/utils/__tests__/getMessageSuppressionReasonBadge.test.ts @@ -0,0 +1,22 @@ +import { getMessageSuppressionReasonBadge } from '@/settings/unsubscribers/utils/getMessageSuppressionReasonBadge'; +import { MessageSuppressionReason } from '~/generated-metadata/graphql'; + +describe('getMessageSuppressionReasonBadge', () => { + it('should badge unsubscribes as gray', () => { + expect( + getMessageSuppressionReasonBadge(MessageSuppressionReason.UNSUBSCRIBE), + ).toEqual({ color: 'gray', label: 'Unsubscribed' }); + }); + + it('should badge complaints as red', () => { + expect( + getMessageSuppressionReasonBadge(MessageSuppressionReason.COMPLAINT), + ).toEqual({ color: 'red', label: 'Complaint' }); + }); + + it('should badge bounces as orange', () => { + expect( + getMessageSuppressionReasonBadge(MessageSuppressionReason.BOUNCE), + ).toEqual({ color: 'orange', label: 'Bounce' }); + }); +}); diff --git a/packages/twenty-front/src/modules/settings/unsubscribers/utils/getMessageSuppressionReasonBadge.ts b/packages/twenty-front/src/modules/settings/unsubscribers/utils/getMessageSuppressionReasonBadge.ts new file mode 100644 index 0000000000..236c17a484 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/unsubscribers/utils/getMessageSuppressionReasonBadge.ts @@ -0,0 +1,23 @@ +import { t } from '@lingui/core/macro'; +import { type ThemeColor } from 'twenty-ui/theme'; +import { MessageSuppressionReason } from '~/generated-metadata/graphql'; + +type MessageSuppressionReasonBadge = { + color: ThemeColor; + label: string; +}; + +export const getMessageSuppressionReasonBadge = ( + reason: MessageSuppressionReason, +): MessageSuppressionReasonBadge => { + switch (reason) { + case MessageSuppressionReason.UNSUBSCRIBE: + return { color: 'gray', label: t`Unsubscribed` }; + case MessageSuppressionReason.COMPLAINT: + return { color: 'red', label: t`Complaint` }; + case MessageSuppressionReason.BOUNCE: + return { color: 'orange', label: t`Bounce` }; + default: + return { color: 'gray', label: t`Unknown` }; + } +}; diff --git a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceCommunicationGroupChannelDetail.tsx b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceCommunicationGroupChannelDetail.tsx index 8611ad8eb5..8017da9c7f 100644 --- a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceCommunicationGroupChannelDetail.tsx +++ b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceCommunicationGroupChannelDetail.tsx @@ -1,10 +1,13 @@ import { useQuery } from '@apollo/client/react'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; +import { useState } from 'react'; import { useParams } from 'react-router-dom'; import { useDeleteEmailGroupChannel } from '@/settings/accounts/hooks/useDeleteEmailGroupChannel'; import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChannels'; +import { useUpdateEmailGroupChannel } from '@/settings/accounts/hooks/useUpdateEmailGroupChannel'; +import { SettingsEditableTitle } from '@/settings/components/SettingsEditableTitle'; import { getEmailChannelDomain } from '@/settings/accounts/utils/getEmailChannelDomain'; import { SettingsDnsRecordsTable } from '@/settings/components/SettingsDnsRecordsTable'; @@ -17,6 +20,7 @@ import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { isNonEmptyString } from '@sniptt/guards'; import { MessageChannelType, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { @@ -70,8 +74,12 @@ export const SettingsWorkspaceCommunicationGroupChannelDetail = () => { const { enqueueErrorSnackBar } = useSnackBar(); const { deleteEmailGroupChannel, loading: deleting } = useDeleteEmailGroupChannel(); + const { updateEmailGroupChannel, loading: updatingDisplayName } = + useUpdateEmailGroupChannel(); const { data: emailingDomainsData } = useQuery(GetEmailingDomainsDocument); + const [displayNameDraft, setDisplayNameDraft] = useState(null); + if (loading) { return ; } @@ -118,6 +126,31 @@ export const SettingsWorkspaceCommunicationGroupChannelDetail = () => { ? EmailingDomainStatus.FAILED : EmailingDomainStatus.PENDING; + const displayName = channel.displayName ?? ''; + + const handleDisplayNameSave = async () => { + if (!isDefined(displayNameDraft) || displayNameDraft === displayName) { + setDisplayNameDraft(null); + + return; + } + + const nextDisplayName = displayNameDraft.trim(); + + try { + await updateEmailGroupChannel( + channel.id, + isNonEmptyString(nextDisplayName) ? nextDisplayName : null, + ); + } catch { + enqueueErrorSnackBar({ + message: t`Failed to update sender name.`, + }); + } finally { + setDisplayNameDraft(null); + } + }; + const handleDelete = async () => { try { await deleteEmailGroupChannel(channel.id); @@ -131,7 +164,19 @@ export const SettingsWorkspaceCommunicationGroupChannelDetail = () => { return ( setDisplayNameDraft(null)} + /> + } links={[ { children: t`Workspace`, @@ -141,6 +186,7 @@ export const SettingsWorkspaceCommunicationGroupChannelDetail = () => { children: t`Communication`, href: getSettingsPath(SettingsPath.WorkspaceCommunications), }, + { children: sourceHandle }, ]} actionButton={ { @@ -39,21 +37,12 @@ export const SettingsWorkspaceCommunications = () => { const { t } = useLingui(); + const navigateSettings = useNavigateSettings(); + const isEmailGroupFeatureEnabled = useIsFeatureEnabled( FeatureFlagKey.IS_EMAIL_GROUP_ENABLED, ); - const apolloClient = useApolloClient(); - - const { data: unsubscribePreviewData } = useQuery<{ - unsubscribePagePreviewUrl: string; - }>(GET_UNSUBSCRIBE_PAGE_PREVIEW_URL, { - client: apolloClient, - skip: !isEmailGroupFeatureEnabled, - }); - - const unsubscribePageUrl = unsubscribePreviewData?.unsubscribePagePreviewUrl; - if (!isEmailGroupFeatureEnabled) { return null; } @@ -99,17 +88,12 @@ export const SettingsWorkspaceCommunications = () => { /> - - + { stroke={theme.icon.stroke.md} /> } - title={t`See unsubscribe page`} + title={t`Manage unsubscribe`} + onClick={() => navigateSettings(SettingsPath.Unsubscribe)} /> - + diff --git a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceNewUnsubscribeTopic.tsx b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceNewUnsubscribeTopic.tsx index 3b2812bcdf..9f952fc16c 100644 --- a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceNewUnsubscribeTopic.tsx +++ b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceNewUnsubscribeTopic.tsx @@ -2,6 +2,7 @@ import { useLingui } from '@lingui/react/macro'; import { useCallback, useState } from 'react'; import { useCreateUnsubscribeTopic } from '@/settings/unsubscribe-topics/hooks/useCreateUnsubscribeTopic'; +import { SETTINGS_UNSUBSCRIBE_TAB_IDS } from '@/settings/unsubscribers/constants/SettingsUnsubscribeTabIds'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; @@ -34,6 +35,18 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => { const canSave = name.length > 0 && !loading; + const navigateToTopics = useCallback( + () => + navigate( + SettingsPath.Unsubscribe, + undefined, + undefined, + undefined, + SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS, + ), + [navigate], + ); + const handleSave = useCallback(async () => { try { const result = await createUnsubscribeTopic({ @@ -50,7 +63,7 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => { unsubscribeTopicId, }); } else { - navigate(SettingsPath.WorkspaceCommunications); + navigateToTopics(); } } catch { enqueueErrorSnackBar({ @@ -63,6 +76,7 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => { description, isPublic, navigate, + navigateToTopics, enqueueErrorSnackBar, t, ]); @@ -83,6 +97,15 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => { children: t`Communication`, href: getSettingsPath(SettingsPath.WorkspaceCommunications), }, + { + children: t`Unsubscribe`, + href: getSettingsPath( + SettingsPath.Unsubscribe, + undefined, + undefined, + SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS, + ), + }, { children: t`New Unsubscribe Topic` }, ]} actionButton={ @@ -90,7 +113,7 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => { isSaveDisabled={!canSave} isCancelDisabled={loading} isLoading={loading} - onCancel={() => navigate(SettingsPath.WorkspaceCommunications)} + onCancel={navigateToTopics} onSave={handleSave} /> } diff --git a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribe.tsx b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribe.tsx new file mode 100644 index 0000000000..5e68602170 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribe.tsx @@ -0,0 +1,84 @@ +import { useLingui } from '@lingui/react/macro'; + +import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; +import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId'; +import { SettingsWorkspaceUnsubscribeTopicSection } from '@/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection'; +import { SettingsUnsubscribePreview } from '@/settings/unsubscribers/components/SettingsUnsubscribePreview'; +import { SettingsUnsubscribersList } from '@/settings/unsubscribers/components/SettingsUnsubscribersList'; +import { SETTINGS_UNSUBSCRIBE_TAB_IDS } from '@/settings/unsubscribers/constants/SettingsUnsubscribeTabIds'; +import { TabList } from '@/ui/layout/tab-list/components/TabList'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types'; +import { getSettingsPath } from 'twenty-shared/utils'; +import { IconEye, IconForbid, IconMailCog } from 'twenty-ui/icon'; + +const UNSUBSCRIBE_TABS_INSTANCE_ID = 'settings-unsubscribe-tabs'; + +export const SettingsWorkspaceUnsubscribe = () => { + const { t } = useLingui(); + + const isEmailGroupEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_EMAIL_GROUP_ENABLED, + ); + + const tabs = [ + { + id: SETTINGS_UNSUBSCRIBE_TAB_IDS.UNSUBSCRIBERS, + title: t`Unsubscribers`, + Icon: IconForbid, + }, + { + id: SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS, + title: t`Topics`, + Icon: IconMailCog, + }, + { + id: SETTINGS_UNSUBSCRIBE_TAB_IDS.PREVIEW, + title: t`Unsubscribe page`, + Icon: IconEye, + }, + ]; + + const activeTabId = useSettingsActiveTabId( + UNSUBSCRIBE_TABS_INSTANCE_ID, + tabs.map((tab) => tab.id), + ); + + if (!isEmailGroupEnabled) { + return null; + } + + return ( + + + + {activeTabId === SETTINGS_UNSUBSCRIBE_TAB_IDS.UNSUBSCRIBERS && ( + + )} + {activeTabId === SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS && ( + + )} + {activeTabId === SETTINGS_UNSUBSCRIBE_TAB_IDS.PREVIEW && ( + + )} + + + ); +}; diff --git a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribeTopicDetail.tsx b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribeTopicDetail.tsx index 7e68668268..c85c65d1cd 100644 --- a/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribeTopicDetail.tsx +++ b/packages/twenty-front/src/pages/settings/communications/SettingsWorkspaceUnsubscribeTopicDetail.tsx @@ -10,6 +10,7 @@ import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsO import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { useDeleteUnsubscribeTopic } from '@/settings/unsubscribe-topics/hooks/useDeleteUnsubscribeTopic'; import { useUpdateUnsubscribeTopic } from '@/settings/unsubscribe-topics/hooks/useUpdateUnsubscribeTopic'; +import { SETTINGS_UNSUBSCRIBE_TAB_IDS } from '@/settings/unsubscribers/constants/SettingsUnsubscribeTabIds'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; @@ -104,7 +105,13 @@ export const SettingsWorkspaceUnsubscribeTopicDetail = () => { const handleDelete = async () => { try { await deleteUnsubscribeTopic(unsubscribeTopic.id); - navigateSettings(SettingsPath.WorkspaceCommunications); + navigateSettings( + SettingsPath.Unsubscribe, + undefined, + undefined, + undefined, + SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS, + ); } catch { enqueueErrorSnackBar({ message: t`Failed to delete unsubscribe topic.`, @@ -126,6 +133,15 @@ export const SettingsWorkspaceUnsubscribeTopicDetail = () => { children: t`Communication`, href: getSettingsPath(SettingsPath.WorkspaceCommunications), }, + { + children: t`Unsubscribe`, + href: getSettingsPath( + SettingsPath.Unsubscribe, + undefined, + undefined, + SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS, + ), + }, { children: topicName }, ]} actionButton={ diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-instance-command-fast-1784904030252-add-message-channel-display-name.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-instance-command-fast-1784904030252-add-message-channel-display-name.ts new file mode 100644 index 0000000000..15ec59d96e --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-instance-command-fast-1784904030252-add-message-channel-display-name.ts @@ -0,0 +1,21 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('2.25.0', 1784904030252) +export class AddMessageChannelDisplayNameFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."messageChannel" ADD COLUMN IF NOT EXISTS "displayName" character varying', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."messageChannel" DROP COLUMN IF EXISTS "displayName"', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index c3accf9ae5..e8456d0958 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -118,6 +118,7 @@ import { AddKanbanAndCalendarWidgetViewTypesFastInstanceCommand } from './2-23/2 import { WidenViewCalendarIntegrityConstraintFastInstanceCommand } from './2-23/2-23-instance-command-fast-1784620626406-widen-view-calendar-integrity-constraint'; import { AddApplicationIdToKeyValuePairFastInstanceCommand } from './2-23/2-23-instance-command-fast-1784659343818-add-application-id-to-key-value-pair'; import { AddLogoFileIdToApplicationRegistration2_23FastInstanceCommand } from './2-23/2-23-instance-command-fast-1784823473532-add-logo-file-id-to-application-registration'; +import { AddMessageChannelDisplayNameFastInstanceCommand } from './2-25/2-25-instance-command-fast-1784904030252-add-message-channel-display-name'; import { BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status'; import { UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784322591746-unlist-unclaimed-npm-application-registrations'; import { AddStatusesToBillingSubscriptionIndexSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784650048045-add-statuses-to-billing-subscription-index'; @@ -247,6 +248,7 @@ export const INSTANCE_COMMANDS = [ AddApplicationIdToKeyValuePairFastInstanceCommand, AddSdkClientCoreChecksumToApplicationFastInstanceCommand, AddLogoFileIdToApplicationRegistration2_23FastInstanceCommand, + AddMessageChannelDisplayNameFastInstanceCommand, AddStatusesToBillingSubscriptionIndexSlowInstanceCommand, AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand, RepairKeyValuePairApplicationIdFastInstanceCommand, diff --git a/packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/find-message-suppressions.input.ts b/packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/find-message-suppressions.input.ts new file mode 100644 index 0000000000..0e161289db --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/find-message-suppressions.input.ts @@ -0,0 +1,40 @@ +import { Field, InputType, Int } from '@nestjs/graphql'; + +import { + IsEnum, + IsOptional, + IsString, + IsUUID, + Max, + Min, +} from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { MessageSuppressionReason } from 'src/engine/core-modules/emailing-domain/types/message-suppression-reason.type'; + +@InputType() +export class FindMessageSuppressionsInput { + @Field(() => MessageSuppressionReason, { nullable: true }) + @IsOptional() + @IsEnum(MessageSuppressionReason) + reason?: MessageSuppressionReason; + + @Field(() => String, { nullable: true }) + @IsOptional() + @IsString() + searchTerm?: string; + + @Field(() => UUIDScalarType, { nullable: true }) + @IsOptional() + @IsUUID('4') + unsubscribeTopicId?: string; + + @Field(() => Int, { defaultValue: 30 }) + @Min(1) + @Max(100) + limit: number; + + @Field(() => Int, { defaultValue: 0 }) + @Min(0) + offset: number; +} diff --git a/packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/message-suppression.dto.ts b/packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/message-suppression.dto.ts new file mode 100644 index 0000000000..7145716376 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/emailing-domain/dtos/message-suppression.dto.ts @@ -0,0 +1,43 @@ +import { Field, Int, ObjectType, registerEnumType } from '@nestjs/graphql'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { MessageSuppressionReason } from 'src/engine/core-modules/emailing-domain/types/message-suppression-reason.type'; +import { MessageSuppressionSource } from 'src/engine/core-modules/emailing-domain/types/message-suppression-source.type'; + +registerEnumType(MessageSuppressionReason, { + name: 'MessageSuppressionReason', +}); + +registerEnumType(MessageSuppressionSource, { + name: 'MessageSuppressionSource', +}); + +@ObjectType('MessageSuppression') +export class MessageSuppressionDTO { + @Field(() => UUIDScalarType) + id: string; + + @Field(() => Date) + createdAt: Date; + + @Field(() => String) + emailAddress: string; + + @Field(() => MessageSuppressionReason) + reason: MessageSuppressionReason; + + @Field(() => MessageSuppressionSource) + source: MessageSuppressionSource; + + @Field(() => UUIDScalarType, { nullable: true }) + unsubscribeTopicId: string | null; +} + +@ObjectType('MessageSuppressionList') +export class MessageSuppressionListDTO { + @Field(() => [MessageSuppressionDTO]) + records: MessageSuppressionDTO[]; + + @Field(() => Int) + totalCount: number; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input.ts index da78a36b77..fa53fe904b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input.ts @@ -1,5 +1,11 @@ import { Field, InputType } from '@nestjs/graphql'; -import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator'; +import { + IsEmail, + IsNotEmpty, + IsOptional, + IsString, + MaxLength, +} from 'class-validator'; @InputType('CreateEmailGroupChannelInput') export class CreateEmailGroupChannelInput { @@ -9,4 +15,10 @@ export class CreateEmailGroupChannelInput { @IsEmail() @MaxLength(254) handle: string; + + @Field({ nullable: true }) + @IsOptional() + @IsString() + @MaxLength(255) + displayName?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/message-channel.dto.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/message-channel.dto.ts index b9aaf6ac3b..5aa70820f8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/message-channel.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/message-channel.dto.ts @@ -39,6 +39,11 @@ export class MessageChannelDTO { @Field() handle: string; + @IsString() + @IsOptional() + @Field(() => String, { nullable: true }) + displayName: string | null; + @IsEnum(MessageChannelType) @IsNotEmpty() @Field(() => MessageChannelType) diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/update-email-group-channel.input.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/update-email-group-channel.input.ts new file mode 100644 index 0000000000..a7434de20f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/dtos/update-email-group-channel.input.ts @@ -0,0 +1,16 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { IsOptional, IsString, MaxLength } from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; + +@InputType('UpdateEmailGroupChannelInput') +export class UpdateEmailGroupChannelInput { + @Field(() => UUIDScalarType) + id: string; + + @Field(() => String, { nullable: true }) + @IsOptional() + @IsString() + @MaxLength(255) + displayName?: string | null; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts index bfa47835bb..eeb3035c85 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts @@ -68,6 +68,9 @@ export class MessageChannelEntity extends WorkspaceRelatedEntity { @Column({ type: 'varchar', nullable: false }) handle: string; + @Column({ type: 'varchar', nullable: true }) + displayName: string | null; + @Column({ type: 'enum', enum: MessageChannelType, diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.service.ts index 435dbbc5aa..3655f18ab8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.service.ts @@ -213,10 +213,12 @@ export class MessageChannelMetadataService { async createEmailGroupChannel({ handle, + displayName, userWorkspaceId, workspaceId, }: { handle: string; + displayName?: string; userWorkspaceId: string; workspaceId: string; }): Promise { @@ -268,9 +270,14 @@ export class MessageChannelMetadataService { visibility: 'workspace', }); + const trimmedDisplayName = displayName?.trim(); + const messageChannel = await this.create({ workspaceId, handle: forwardingAddress, + displayName: isNonEmptyString(trimmedDisplayName) + ? trimmedDisplayName + : null, connectedAccountId: connectedAccount.id, type: MessageChannelType.EMAIL_GROUP, visibility: MessageChannelVisibility.SHARE_EVERYTHING, @@ -340,6 +347,48 @@ export class MessageChannelMetadataService { return messageChannel; } + async updateEmailGroupChannel({ + id, + displayName, + userWorkspaceId, + workspaceId, + }: { + id: string; + displayName?: string | null; + userWorkspaceId: string; + workspaceId: string; + }): Promise { + const messageChannel = await this.verifyOwnership({ + id, + userWorkspaceId, + workspaceId, + }); + + if (messageChannel.type !== MessageChannelType.EMAIL_GROUP) { + throw new MessageChannelException( + `Message channel ${id} is not an email group`, + MessageChannelExceptionCode.INVALID_MESSAGE_CHANNEL_INPUT, + ); + } + + // An omitted displayName leaves the current one untouched; an explicit null clears it + if (displayName === undefined) { + return messageChannel; + } + + const trimmedDisplayName = displayName?.trim(); + + return this.update({ + id, + workspaceId, + data: { + displayName: isNonEmptyString(trimmedDisplayName) + ? trimmedDisplayName + : null, + }, + }); + } + async deleteEmailGroupChannel({ id, userWorkspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts index 76dcdb653a..e7e354db3e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts @@ -17,6 +17,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; import { ConnectedAccountPublicDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto'; import { CreateEmailGroupChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input'; +import { UpdateEmailGroupChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/update-email-group-channel.input'; import { CreateEmailGroupChannelOutput } from 'src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.output'; import { MessageChannelDTO } from 'src/engine/metadata-modules/message-channel/dtos/message-channel.dto'; import { UpdateMessageChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/update-message-channel.input'; @@ -174,6 +175,22 @@ export class MessageChannelResolver { ): Promise { return this.messageChannelMetadataService.createEmailGroupChannel({ handle: input.handle, + displayName: input.displayName, + userWorkspaceId, + workspaceId: workspace.id, + }); + } + + @Mutation(() => MessageChannelDTO) + @UseGuards(NoPermissionGuard) + async updateEmailGroupChannel( + @Args('input') input: UpdateEmailGroupChannelInput, + @AuthWorkspace() workspace: WorkspaceEntity, + @AuthUserWorkspaceId() userWorkspaceId: string, + ): Promise { + return this.messageChannelMetadataService.updateEmailGroupChannel({ + id: input.id, + displayName: input.displayName, userWorkspaceId, workspaceId: workspace.id, }); diff --git a/packages/twenty-server/src/modules/emailing/emailing.module.ts b/packages/twenty-server/src/modules/emailing/emailing.module.ts index 35e2afc716..8e6c267ed2 100644 --- a/packages/twenty-server/src/modules/emailing/emailing.module.ts +++ b/packages/twenty-server/src/modules/emailing/emailing.module.ts @@ -16,6 +16,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; import { UnsubscribeController } from 'src/modules/emailing/controllers/unsubscribe.controller'; import { EmailingSendResolver } from 'src/modules/emailing/resolvers/emailing-send.resolver'; +import { MessageSuppressionResolver } from 'src/modules/emailing/resolvers/message-suppression.resolver'; import { UnsubscribeTopicResolver } from 'src/modules/emailing/resolvers/unsubscribe-topic.resolver'; import { EmailBillingService } from 'src/modules/emailing/services/email-billing.service'; import { EmailingDomainSenderService } from 'src/modules/emailing/services/emailing-domain-sender.service'; @@ -50,6 +51,7 @@ import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscri UnsubscribeTopicService, EmailingDomainSenderService, EmailingSendResolver, + MessageSuppressionResolver, UnsubscribeTopicResolver, provideWorkspaceScopedRepository(EmailingDomainEntity), provideWorkspaceScopedRepository(MessageSuppressionEntity), diff --git a/packages/twenty-server/src/modules/emailing/resolvers/message-suppression.resolver.ts b/packages/twenty-server/src/modules/emailing/resolvers/message-suppression.resolver.ts new file mode 100644 index 0000000000..0ddf62d1a3 --- /dev/null +++ b/packages/twenty-server/src/modules/emailing/resolvers/message-suppression.resolver.ts @@ -0,0 +1,58 @@ +import { UseFilters, UseGuards, UsePipes } from '@nestjs/common'; +import { Args, Query } from '@nestjs/graphql'; + +import { PermissionFlagType } from 'twenty-shared/constants'; +import { FeatureFlagKey } from 'twenty-shared/types'; + +import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; +import { FindMessageSuppressionsInput } from 'src/engine/core-modules/emailing-domain/dtos/find-message-suppressions.input'; +import { MessageSuppressionListDTO } from 'src/engine/core-modules/emailing-domain/dtos/message-suppression.dto'; +import { EmailGroupAccessGraphqlApiExceptionFilter } from 'src/engine/core-modules/emailing-domain/filters/email-group-access-graphql-api-exception.filter'; +import { EmailingDomainGraphqlApiExceptionFilter } from 'src/engine/core-modules/emailing-domain/filters/emailing-domain-graphql-api-exception.filter'; +import { EmailGroupAccessService } from 'src/engine/core-modules/emailing-domain/services/email-group-access.service'; +import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { + FeatureFlagGuard, + RequireFeatureFlag, +} from 'src/engine/guards/feature-flag.guard'; +import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { MessageSuppressionService } from 'src/modules/emailing/services/message-suppression.service'; + +@UseGuards( + WorkspaceAuthGuard, + FeatureFlagGuard, + SettingsPermissionGuard(PermissionFlagType.WORKSPACE), +) +@UseFilters( + EmailGroupAccessGraphqlApiExceptionFilter, + EmailingDomainGraphqlApiExceptionFilter, +) +@UsePipes(ResolverValidationPipe) +@MetadataResolver(() => MessageSuppressionListDTO) +export class MessageSuppressionResolver { + constructor( + private readonly messageSuppressionService: MessageSuppressionService, + private readonly emailGroupAccessService: EmailGroupAccessService, + ) {} + + @Query(() => MessageSuppressionListDTO) + @RequireFeatureFlag(FeatureFlagKey.IS_EMAIL_GROUP_ENABLED) + async messageSuppressions( + @Args('input') input: FindMessageSuppressionsInput, + @AuthWorkspace() currentWorkspace: WorkspaceEntity, + ): Promise { + this.emailGroupAccessService.validateEmailGroupAccessOrThrow(); + + return this.messageSuppressionService.findSuppressions({ + workspaceId: currentWorkspace.id, + reason: input.reason, + searchTerm: input.searchTerm, + unsubscribeTopicId: input.unsubscribeTopicId, + limit: input.limit, + offset: input.offset, + }); + } +} diff --git a/packages/twenty-server/src/modules/emailing/resolvers/unsubscribe-topic.resolver.ts b/packages/twenty-server/src/modules/emailing/resolvers/unsubscribe-topic.resolver.ts index d42d5413b7..42491314e8 100644 --- a/packages/twenty-server/src/modules/emailing/resolvers/unsubscribe-topic.resolver.ts +++ b/packages/twenty-server/src/modules/emailing/resolvers/unsubscribe-topic.resolver.ts @@ -10,9 +10,7 @@ import { UnsubscribeTopicDTO } from 'src/engine/core-modules/emailing-domain/dto import { UpdateUnsubscribeTopicInput } from 'src/engine/core-modules/emailing-domain/dtos/update-unsubscribe-topic.input'; import { EmailGroupAccessGraphqlApiExceptionFilter } from 'src/engine/core-modules/emailing-domain/filters/email-group-access-graphql-api-exception.filter'; import { EmailGroupAccessService } from 'src/engine/core-modules/emailing-domain/services/email-group-access.service'; -import { UnsubscribeTokenService } from 'src/engine/core-modules/emailing-domain/services/unsubscribe-token.service'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { @@ -23,8 +21,6 @@ import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.g import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscribe-topic.service'; -const UNSUBSCRIBE_PREVIEW_PLACEHOLDER_EMAIL = 'preview@example.com'; - @UseGuards( WorkspaceAuthGuard, FeatureFlagGuard, @@ -36,8 +32,6 @@ const UNSUBSCRIBE_PREVIEW_PLACEHOLDER_EMAIL = 'preview@example.com'; export class UnsubscribeTopicResolver { constructor( private readonly unsubscribeTopicService: UnsubscribeTopicService, - private readonly unsubscribeTokenService: UnsubscribeTokenService, - private readonly twentyConfigService: TwentyConfigService, private readonly emailGroupAccessService: EmailGroupAccessService, ) {} @@ -53,22 +47,6 @@ export class UnsubscribeTopicResolver { ); } - @Query(() => String) - @RequireFeatureFlag(FeatureFlagKey.IS_EMAIL_GROUP_ENABLED) - unsubscribePagePreviewUrl( - @AuthWorkspace() currentWorkspace: WorkspaceEntity, - ): string { - this.emailGroupAccessService.validateEmailGroupAccessOrThrow(); - - const token = this.unsubscribeTokenService.sign({ - workspaceId: currentWorkspace.id, - emailAddress: UNSUBSCRIBE_PREVIEW_PLACEHOLDER_EMAIL, - preview: true, - }); - - return `${this.twentyConfigService.get('SERVER_URL')}/emailing/unsubscribe?t=${token}`; - } - @Mutation(() => UnsubscribeTopicDTO) @RequireFeatureFlag(FeatureFlagKey.IS_EMAIL_GROUP_ENABLED) async createUnsubscribeTopic( diff --git a/packages/twenty-server/src/modules/emailing/services/emailing-domain-sender.service.ts b/packages/twenty-server/src/modules/emailing/services/emailing-domain-sender.service.ts index 5fe34048b1..66a8a5ab37 100644 --- a/packages/twenty-server/src/modules/emailing/services/emailing-domain-sender.service.ts +++ b/packages/twenty-server/src/modules/emailing/services/emailing-domain-sender.service.ts @@ -16,6 +16,7 @@ import { type EmailingDomainEmailContent } from 'src/engine/core-modules/emailin import { type EmailingDomainSendEmailRequest } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-input.type'; import { type EmailingDomainSendEmailResult } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-result.type'; import { EmailingDomainEntity } from 'src/engine/core-modules/emailing-domain/emailing-domain.entity'; +import { formatMessageFromHeader } from 'src/modules/messaging/message-outbound-manager/utils/format-message-from-header.util'; import { MessageSuppressionService } from 'src/modules/emailing/services/message-suppression.service'; import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { type DeliverableRecipients } from 'src/engine/core-modules/emailing-domain/types/deliverable-recipients.type'; @@ -53,10 +54,19 @@ export class EmailingDomainSenderService { emailContent, ); - const replyTo = await this.resolveReplyTo(workspaceId, emailContent); + const emailGroupChannel = await this.findEmailGroupChannel( + workspaceId, + emailContent.from, + ); + + const replyTo = this.resolveReplyTo(emailContent, emailGroupChannel); const emailToSend: EmailingDomainSendEmailRequest = { ...emailContent, + from: formatMessageFromHeader({ + fromEmail: emailContent.from, + fromName: emailGroupChannel?.displayName, + }), workspaceId, domain: emailingDomain.domain, emailingDomain, @@ -71,22 +81,27 @@ export class EmailingDomainSenderService { .sendEmail(emailToSend); } - private async resolveReplyTo( + private async findEmailGroupChannel( workspaceId: string, - emailContent: EmailingDomainEmailContent, - ): Promise { - if (isDefined(emailContent.replyTo) && emailContent.replyTo.length > 0) { - return emailContent.replyTo; - } - - const emailGroupChannel = await this.messageChannelRepository.findOne({ + fromAddress: string, + ): Promise { + return this.messageChannelRepository.findOne({ where: { workspaceId, type: MessageChannelType.EMAIL_GROUP, - connectedAccount: { handle: emailContent.from }, + connectedAccount: { handle: fromAddress }, }, relations: { connectedAccount: true }, }); + } + + private resolveReplyTo( + emailContent: EmailingDomainEmailContent, + emailGroupChannel: MessageChannelEntity | null, + ): string[] | undefined { + if (isDefined(emailContent.replyTo) && emailContent.replyTo.length > 0) { + return emailContent.replyTo; + } const forwardingAddress = emailGroupChannel?.handle; diff --git a/packages/twenty-server/src/modules/emailing/services/message-suppression.service.ts b/packages/twenty-server/src/modules/emailing/services/message-suppression.service.ts index 0ba92f68bd..c411bf8a57 100644 --- a/packages/twenty-server/src/modules/emailing/services/message-suppression.service.ts +++ b/packages/twenty-server/src/modules/emailing/services/message-suppression.service.ts @@ -1,8 +1,12 @@ import { Injectable } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; -import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; -import { In, IsNull, QueryFailedError } from 'typeorm'; +import { + escapeForIlike, + isDefined, + isNonEmptyArray, +} from 'twenty-shared/utils'; +import { ILike, In, IsNull, QueryFailedError } from 'typeorm'; import { POSTGRESQL_ERROR_CODES } from 'src/engine/api/graphql/workspace-query-runner/constants/postgres-error-codes.constants'; import { type QueryFailedErrorWithCode } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; @@ -18,6 +22,15 @@ import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository'; import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscribe-topic.service'; +type FindSuppressionsArgs = { + workspaceId: string; + reason?: MessageSuppressionReason; + searchTerm?: string; + unsubscribeTopicId?: string; + limit: number; + offset: number; +}; + type SuppressArgs = { workspaceId: string; emailAddress: string; @@ -46,6 +59,38 @@ export class MessageSuppressionService { private readonly unsubscribeTopicService: UnsubscribeTopicService, ) {} + async findSuppressions({ + workspaceId, + reason, + searchTerm, + unsubscribeTopicId, + limit, + offset, + }: FindSuppressionsArgs): Promise<{ + records: MessageSuppressionEntity[]; + totalCount: number; + }> { + const [records, totalCount] = await this.suppressionRepository.findAndCount( + workspaceId, + { + where: { + ...(isDefined(reason) ? { reason } : {}), + ...(isNonEmptyString(unsubscribeTopicId) + ? { unsubscribeTopicId } + : {}), + ...(isNonEmptyString(searchTerm) + ? { emailAddress: ILike(`%${escapeForIlike(searchTerm)}%`) } + : {}), + }, + order: { createdAt: 'DESC' }, + take: limit, + skip: offset, + }, + ); + + return { records, totalCount }; + } + async getSuppressedAddresses( workspaceId: string, emailAddresses: string[], diff --git a/packages/twenty-shared/src/types/SettingsPath.ts b/packages/twenty-shared/src/types/SettingsPath.ts index d71b09c098..5b6a564126 100644 --- a/packages/twenty-shared/src/types/SettingsPath.ts +++ b/packages/twenty-shared/src/types/SettingsPath.ts @@ -33,6 +33,7 @@ export enum SettingsPath { NewEmailGroupChannel = 'email/new-email-group', NewUnsubscribeTopic = 'email/new-unsubscribe-topic', UnsubscribeTopicDetail = 'email/unsubscribe-topic/:unsubscribeTopicId', + Unsubscribe = 'communications/unsubscribe', PublicDomain = 'applications/public-domain', NewApprovedAccessDomain = 'security/approved-access-domain/new', Community = 'community',