Feat/email settings v2 (#23180)
Settings pages changes - Add `displayName` - Unsubscribers Page <img width="1496" height="844" alt="Screenshot 2026-07-22 at 8 52 15 PM" src="https://github.com/user-attachments/assets/69bc1993-4547-4a64-83a6-b47fef1a4e40" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23180?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1137,6 +1137,7 @@ export type CreateCommandMenuItemInput = {
|
||||
};
|
||||
|
||||
export type CreateEmailGroupChannelInput = {
|
||||
displayName?: InputMaybe<Scalars['String']['input']>;
|
||||
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<MessageSuppressionReason>;
|
||||
searchTerm?: InputMaybe<Scalars['String']['input']>;
|
||||
unsubscribeTopicId?: InputMaybe<Scalars['UUID']['input']>;
|
||||
};
|
||||
|
||||
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<Scalars['String']['output']>;
|
||||
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<Scalars['UUID']['output']>;
|
||||
};
|
||||
|
||||
export type MessageSuppressionList = {
|
||||
__typename?: 'MessageSuppressionList';
|
||||
records: Array<MessageSuppression>;
|
||||
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<NavigationMenuItem>;
|
||||
@@ -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<BillingPlan>;
|
||||
messageSuppressions: MessageSuppressionList;
|
||||
minimalMetadata: MinimalMetadata;
|
||||
mostlyEmptyFieldMetadataIds: Array<Scalars['UUID']['output']>;
|
||||
myCalendarChannels: Array<CalendarChannel>;
|
||||
@@ -4474,7 +4518,6 @@ export type Query = {
|
||||
publicMarketplaceApps: Array<MarketplaceApp>;
|
||||
skill?: Maybe<Skill>;
|
||||
skills: Array<Skill>;
|
||||
unsubscribePagePreviewUrl: Scalars['String']['output'];
|
||||
unsubscribeTopics: Array<UnsubscribeTopic>;
|
||||
validatePasswordResetToken: ValidatePasswordResetToken;
|
||||
webhook?: Maybe<Webhook>;
|
||||
@@ -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<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type UpdateEmailGroupChannelInput = {
|
||||
displayName?: InputMaybe<Scalars['String']['input']>;
|
||||
id: Scalars['UUID']['input'];
|
||||
};
|
||||
|
||||
export type UpdateFieldInput = {
|
||||
defaultValue?: InputMaybe<Scalars['JSON']['input']>;
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
@@ -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<Scalars['UUID']['input']>;
|
||||
@@ -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<UsageAnalyticsInput>;
|
||||
@@ -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<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>;
|
||||
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<StartChannelSyncMutation, StartChannelSyncMutationVariables>;
|
||||
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<UpdateCalendarChannelMutation, UpdateCalendarChannelMutationVariables>;
|
||||
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<UpdateEmailGroupChannelMutation, UpdateEmailGroupChannelMutationVariables>;
|
||||
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<UpdateMessageChannelMutation, UpdateMessageChannelMutationVariables>;
|
||||
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<UpdateMessageFolderMutation, UpdateMessageFolderMutationVariables>;
|
||||
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<UpdateMessageFoldersMutation, UpdateMessageFoldersMutationVariables>;
|
||||
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<GetConnectedImapSmtpCaldavAccountQuery, GetConnectedImapSmtpCaldavAccountQueryVariables>;
|
||||
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<MyCalendarChannelsQuery, MyCalendarChannelsQueryVariables>;
|
||||
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<MyConnectedAccountsQuery, MyConnectedAccountsQueryVariables>;
|
||||
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<MyMessageChannelsQuery, MyMessageChannelsQueryVariables>;
|
||||
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<MyMessageChannelsQuery, MyMessageChannelsQueryVariables>;
|
||||
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<MyMessageFoldersQuery, MyMessageFoldersQueryVariables>;
|
||||
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<ClaimApplicationRegistrationOwnershipMutation, ClaimApplicationRegistrationOwnershipMutationVariables>;
|
||||
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<DeleteApplicationRegistrationMutation, DeleteApplicationRegistrationMutationVariables>;
|
||||
@@ -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<CreateUnsubscribeTopicMutation, CreateUnsubscribeTopicMutationVariables>;
|
||||
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<DeleteUnsubscribeTopicMutation, DeleteUnsubscribeTopicMutationVariables>;
|
||||
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<UpdateUnsubscribeTopicMutation, UpdateUnsubscribeTopicMutationVariables>;
|
||||
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<UnsubscribePagePreviewUrlQuery, UnsubscribePagePreviewUrlQueryVariables>;
|
||||
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<MessageSuppressionsQuery, MessageSuppressionsQueryVariables>;
|
||||
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<GetUsageAnalyticsQuery, GetUsageAnalyticsQueryVariables>;
|
||||
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<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
||||
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<DeleteUserWorkspaceMutation, DeleteUserWorkspaceMutationVariables>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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={<SettingsWorkspaceUnsubscribeTopicDetail />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.Unsubscribe}
|
||||
element={<SettingsWorkspaceUnsubscribe />}
|
||||
/>
|
||||
<Route path={SettingsPath.Billing} element={<SettingsBilling />} />
|
||||
<Route
|
||||
path={SettingsPath.BillingPlans}
|
||||
|
||||
+27
-2
@@ -20,12 +20,17 @@ export const SettingsAccountsNewEmailGroupChannel = () => {
|
||||
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 (
|
||||
<SettingsPageLayout
|
||||
@@ -80,6 +85,26 @@ export const SettingsAccountsNewEmailGroupChannel = () => {
|
||||
disabled={loading}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Display Name`}
|
||||
description={t`The name recipients see next to your address in their inbox, instead of the address alone.`}
|
||||
/>
|
||||
<SettingsTextInput
|
||||
instanceId="email-group-display-name"
|
||||
label={t`Display Name`}
|
||||
placeholder={t`Support Team`}
|
||||
value={displayName}
|
||||
maxLength={255}
|
||||
onChange={setDisplayName}
|
||||
onInputEnter={() => {
|
||||
if (canSave) {
|
||||
handleSave();
|
||||
}
|
||||
}}
|
||||
disabled={loading}
|
||||
/>
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SettingsPageLayout>
|
||||
);
|
||||
|
||||
+10
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
+1
@@ -5,6 +5,7 @@ export const GET_MY_MESSAGE_CHANNELS = gql`
|
||||
myMessageChannels(connectedAccountId: $connectedAccountId) {
|
||||
id
|
||||
handle
|
||||
displayName
|
||||
visibility
|
||||
type
|
||||
isContactAutoCreationEnabled
|
||||
|
||||
+3
-2
@@ -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 });
|
||||
},
|
||||
|
||||
+32
@@ -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 };
|
||||
};
|
||||
@@ -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 (
|
||||
<StyledContainer>
|
||||
<Button
|
||||
title={t`Previous`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={page === 0 || isLoading}
|
||||
onClick={() => onPageChange(page - 1)}
|
||||
/>
|
||||
<div>{t`Page ${page + 1} of ${pageCount}`}</div>
|
||||
<Button
|
||||
title={t`Next`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={isNextDisabled || isLoading}
|
||||
onClick={() => onPageChange(page + 1)}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
+18
-11
@@ -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<Item extends { id: string }> = {
|
||||
title: string;
|
||||
description: string;
|
||||
headerAdornment?: ReactNode;
|
||||
toolbar?: ReactNode;
|
||||
items: Item[];
|
||||
columns: SettingsTableListSectionColumn<Item>[];
|
||||
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 && (
|
||||
<Table>
|
||||
<TableRow gridAutoColumns={resolvedGridAutoColumns}>
|
||||
@@ -124,15 +129,17 @@ export const SettingsTableListSection = <
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
)}
|
||||
<StyledFooter>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={footerButtonLabel}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onFooterButtonClick}
|
||||
/>
|
||||
</StyledFooter>
|
||||
{isDefined(footerButtonLabel) && isDefined(onFooterButtonClick) && (
|
||||
<StyledFooter>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={footerButtonLabel}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onFooterButtonClick}
|
||||
/>
|
||||
</StyledFooter>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_UNSUBSCRIBE_PAGE_PREVIEW_URL = gql`
|
||||
query UnsubscribePagePreviewUrl {
|
||||
unsubscribePagePreviewUrl
|
||||
}
|
||||
`;
|
||||
+107
@@ -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 (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Unsubscribe page`}
|
||||
description={t`Preview of the page recipients see when they unsubscribe`}
|
||||
/>
|
||||
<StyledViewport>
|
||||
<StyledCard rounded>
|
||||
<StyledHeader>
|
||||
<H2Title
|
||||
title={t`Do you want to unsubscribe?`}
|
||||
description={t`Confirm your preferences:`}
|
||||
/>
|
||||
</StyledHeader>
|
||||
<StyledTopics>
|
||||
{publicTopics.map((topic) => (
|
||||
<StyledTopicRow key={topic.id}>
|
||||
<Checkbox
|
||||
checked
|
||||
onChange={() => {}}
|
||||
aria-label={topic.name ?? t`Untitled topic`}
|
||||
/>
|
||||
{topic.name ?? t`Untitled topic`}
|
||||
</StyledTopicRow>
|
||||
))}
|
||||
</StyledTopics>
|
||||
<Button
|
||||
title={t`Update`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
fullWidth
|
||||
justify="center"
|
||||
/>
|
||||
<HorizontalSeparator text={t`Or`} noMargin />
|
||||
<Button
|
||||
title={t`Unsubscribe all`}
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
justify="center"
|
||||
/>
|
||||
</StyledCard>
|
||||
</StyledViewport>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
+196
@@ -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 (
|
||||
<StyledContainer>
|
||||
<SettingsTableListSection<MessageSuppression>
|
||||
title={t`Unsubscribers`}
|
||||
description={t`Email addresses that will no longer receive campaign emails`}
|
||||
toolbar={
|
||||
<StyledToolbar>
|
||||
<StyledSearch>
|
||||
<SearchInput
|
||||
placeholder={t`Search by email address`}
|
||||
value={searchText}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</StyledSearch>
|
||||
<SettingsUnsubscribersFilterDropdown
|
||||
topics={unsubscribeTopics}
|
||||
reasonValue={reasonFilter}
|
||||
topicValue={topicFilter}
|
||||
onChangeReason={handleReasonChange}
|
||||
onChangeTopic={handleTopicChange}
|
||||
onClear={handleClearFilters}
|
||||
/>
|
||||
</StyledToolbar>
|
||||
}
|
||||
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 (
|
||||
<Status
|
||||
color={badge.color}
|
||||
text={badge.label}
|
||||
weight="medium"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t`Date`,
|
||||
align: 'right',
|
||||
Cell: ({ item }) => (
|
||||
<>{formatToHumanReadableDate(item.createdAt)}</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
gridAutoColumns="1fr 160px 140px 160px"
|
||||
/>
|
||||
{loading && <SettingsSectionSkeletonLoader />}
|
||||
{isEmpty && (
|
||||
<SettingsEmptyPlaceholder>
|
||||
{t`No unsubscribers found`}
|
||||
</SettingsEmptyPlaceholder>
|
||||
)}
|
||||
{hasPagination && (
|
||||
<SettingsPaginationControls
|
||||
page={page}
|
||||
pageCount={pageCount}
|
||||
onPageChange={setPage}
|
||||
isLoading={loading}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
+123
@@ -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<SettingsUnsubscribersFilterContentId | null>(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 (
|
||||
<SettingsUnsubscribersFilterOptionsContent
|
||||
title={t`Reason`}
|
||||
options={reasonOptions}
|
||||
selectedValue={reasonValue}
|
||||
onSelect={onChangeReason}
|
||||
onBack={goToMenu}
|
||||
/>
|
||||
);
|
||||
case 'topic':
|
||||
return (
|
||||
<SettingsUnsubscribersFilterOptionsContent
|
||||
title={t`Topic`}
|
||||
options={topicOptions}
|
||||
selectedValue={topicValue}
|
||||
onSelect={onChangeTopic}
|
||||
onBack={goToMenu}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<SettingsUnsubscribersFilterMenuContent
|
||||
reasonLabel={getOptionLabel(reasonOptions, reasonValue)}
|
||||
topicLabel={getOptionLabel(topicOptions, topicValue)}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
onContentChange={setContentId}
|
||||
onClear={onClear}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={SETTINGS_UNSUBSCRIBERS_FILTER_DROPDOWN_ID}
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
onClose={goToMenu}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter unsubscribers`}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={renderContent()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+60
@@ -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 (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconStatusChange}
|
||||
text={t`Reason`}
|
||||
contextualText={reasonLabel}
|
||||
contextualTextPosition="right"
|
||||
hasSubMenu
|
||||
onClick={() => onContentChange('reason')}
|
||||
/>
|
||||
<MenuItem
|
||||
LeftIcon={IconMailCog}
|
||||
text={t`Topic`}
|
||||
contextualText={topicLabel}
|
||||
contextualTextPosition="right"
|
||||
hasSubMenu
|
||||
onClick={() => onContentChange('topic')}
|
||||
/>
|
||||
{hasActiveFilters && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text={t`Clear filters`}
|
||||
onClick={onClear}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
+57
@@ -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 (
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader
|
||||
StartComponent={
|
||||
<DropdownMenuHeaderLeftComponent
|
||||
onClick={onBack}
|
||||
Icon={IconChevronLeft}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{title}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{options.map((option) => (
|
||||
<MenuItemSelect
|
||||
key={option.value}
|
||||
text={option.label}
|
||||
selected={selectedValue === option.value}
|
||||
onClick={() => handleSelect(option.value)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export type SettingsUnsubscribersFilterContentId = 'reason' | 'topic';
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type SettingsUnsubscribersFilterOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const MESSAGE_SUPPRESSIONS_PAGE_SIZE = 30;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export const SETTINGS_UNSUBSCRIBE_TAB_IDS = {
|
||||
UNSUBSCRIBERS: 'unsubscribers',
|
||||
TOPICS: 'topics',
|
||||
PREVIEW: 'preview',
|
||||
} as const;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const SETTINGS_UNSUBSCRIBERS_ALL_FILTER = 'all';
|
||||
+16
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
+43
@@ -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,
|
||||
};
|
||||
};
|
||||
+22
@@ -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' });
|
||||
});
|
||||
});
|
||||
+23
@@ -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` };
|
||||
}
|
||||
};
|
||||
+47
-1
@@ -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<string | null>(null);
|
||||
|
||||
if (loading) {
|
||||
return <SettingsSkeletonLoader />;
|
||||
}
|
||||
@@ -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 (
|
||||
<SettingsPageLayout
|
||||
title={sourceHandle}
|
||||
title={
|
||||
<SettingsEditableTitle
|
||||
instanceId="email-group-display-name"
|
||||
value={displayNameDraft ?? displayName}
|
||||
placeholder={t`Sender name`}
|
||||
disabled={updatingDisplayName}
|
||||
onChange={setDisplayNameDraft}
|
||||
onEnter={handleDisplayNameSave}
|
||||
onTab={handleDisplayNameSave}
|
||||
onClickOutside={handleDisplayNameSave}
|
||||
onEscape={() => setDisplayNameDraft(null)}
|
||||
/>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
@@ -141,6 +186,7 @@ export const SettingsWorkspaceCommunicationGroupChannelDetail = () => {
|
||||
children: t`Communication`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceCommunications),
|
||||
},
|
||||
{ children: sourceHandle },
|
||||
]}
|
||||
actionButton={
|
||||
<Button
|
||||
|
||||
+13
-28
@@ -1,11 +1,8 @@
|
||||
import { useApolloClient, useQuery } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
|
||||
import { GET_UNSUBSCRIBE_PAGE_PREVIEW_URL } from '@/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl';
|
||||
import { SettingsWorkspaceUnsubscribeTopicSection } from '@/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection';
|
||||
import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
@@ -24,14 +21,15 @@ import coverDark from '~/pages/settings/communications/assets/cover-dark.png';
|
||||
import coverLight from '~/pages/settings/communications/assets/cover-light.png';
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const COMMUNICATIONS_TABS_INSTANCE_ID = 'settings-communications-tabs';
|
||||
|
||||
const StyledCardLink = styled.a`
|
||||
display: block;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
const StyledCardsColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const SettingsWorkspaceCommunications = () => {
|
||||
@@ -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 = () => {
|
||||
/>
|
||||
</Section>
|
||||
<SettingsWorkspaceEmailGroupSection />
|
||||
<SettingsWorkspaceUnsubscribeTopicSection />
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Unsubscribe`}
|
||||
description={t`The page your users will get redirected to to unsubscribe from your emails`}
|
||||
description={t`Manage unsubscribers, opt-out topics, and the page recipients see`}
|
||||
/>
|
||||
<StyledCardLink
|
||||
href={unsubscribePageUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<StyledCardsColumn>
|
||||
<SettingsCard
|
||||
Icon={
|
||||
<IconMailX
|
||||
@@ -117,9 +101,10 @@ export const SettingsWorkspaceCommunications = () => {
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
}
|
||||
title={t`See unsubscribe page`}
|
||||
title={t`Manage unsubscribe`}
|
||||
onClick={() => navigateSettings(SettingsPath.Unsubscribe)}
|
||||
/>
|
||||
</StyledCardLink>
|
||||
</StyledCardsColumn>
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SettingsPageLayout>
|
||||
|
||||
+25
-2
@@ -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}
|
||||
/>
|
||||
}
|
||||
|
||||
+84
@@ -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 (
|
||||
<SettingsPageLayout
|
||||
title={t`Unsubscribe`}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.General),
|
||||
},
|
||||
{
|
||||
children: t`Communication`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceCommunications),
|
||||
},
|
||||
{ children: t`Unsubscribe` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
componentInstanceId={UNSUBSCRIBE_TABS_INSTANCE_ID}
|
||||
/>
|
||||
{activeTabId === SETTINGS_UNSUBSCRIBE_TAB_IDS.UNSUBSCRIBERS && (
|
||||
<SettingsUnsubscribersList />
|
||||
)}
|
||||
{activeTabId === SETTINGS_UNSUBSCRIBE_TAB_IDS.TOPICS && (
|
||||
<SettingsWorkspaceUnsubscribeTopicSection />
|
||||
)}
|
||||
{activeTabId === SETTINGS_UNSUBSCRIBE_TAB_IDS.PREVIEW && (
|
||||
<SettingsUnsubscribePreview />
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
</SettingsPageLayout>
|
||||
);
|
||||
};
|
||||
+17
-1
@@ -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={
|
||||
|
||||
+21
@@ -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<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."messageChannel" ADD COLUMN IF NOT EXISTS "displayName" character varying',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."messageChannel" DROP COLUMN IF EXISTS "displayName"',
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -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,
|
||||
|
||||
+40
@@ -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;
|
||||
}
|
||||
+43
@@ -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;
|
||||
}
|
||||
+13
-1
@@ -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;
|
||||
}
|
||||
|
||||
+5
@@ -39,6 +39,11 @@ export class MessageChannelDTO {
|
||||
@Field()
|
||||
handle: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field(() => String, { nullable: true })
|
||||
displayName: string | null;
|
||||
|
||||
@IsEnum(MessageChannelType)
|
||||
@IsNotEmpty()
|
||||
@Field(() => MessageChannelType)
|
||||
|
||||
+16
@@ -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;
|
||||
}
|
||||
+3
@@ -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,
|
||||
|
||||
+49
@@ -213,10 +213,12 @@ export class MessageChannelMetadataService {
|
||||
|
||||
async createEmailGroupChannel({
|
||||
handle,
|
||||
displayName,
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
handle: string;
|
||||
displayName?: string;
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<CreateEmailGroupChannelOutput> {
|
||||
@@ -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<MessageChannelDTO> {
|
||||
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,
|
||||
|
||||
+17
@@ -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<CreateEmailGroupChannelOutput> {
|
||||
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<MessageChannelDTO> {
|
||||
return this.messageChannelMetadataService.updateEmailGroupChannel({
|
||||
id: input.id,
|
||||
displayName: input.displayName,
|
||||
userWorkspaceId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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<MessageSuppressionListDTO> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
+25
-10
@@ -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<string[] | undefined> {
|
||||
if (isDefined(emailContent.replyTo) && emailContent.replyTo.length > 0) {
|
||||
return emailContent.replyTo;
|
||||
}
|
||||
|
||||
const emailGroupChannel = await this.messageChannelRepository.findOne({
|
||||
fromAddress: string,
|
||||
): Promise<MessageChannelEntity | null> {
|
||||
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;
|
||||
|
||||
|
||||
+47
-2
@@ -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[],
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user