From e0d88df88accb25af83c0357529814d2aaae25b0 Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Mon, 10 Jun 2024 11:19:50 +0200 Subject: [PATCH 1/8] Add brevo contact edit form to admin package --- .../src/brevoContacts/BrevoContactsGrid.tsx | 73 ++++--- .../src/brevoContacts/BrevoContactsPage.tsx | 44 +++- .../form/BrevoContactForm.gql.ts | 42 ++++ .../brevoContacts/form/BrevoContactForm.tsx | 193 ++++++++++++++++++ packages/admin/src/index.ts | 1 + 5 files changed, 314 insertions(+), 39 deletions(-) create mode 100644 packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts create mode 100644 packages/admin/src/brevoContacts/form/BrevoContactForm.tsx diff --git a/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx b/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx index df8fe670..9f7bf9b8 100644 --- a/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx +++ b/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx @@ -4,6 +4,7 @@ import { messages, RowActionsItem, RowActionsMenu, + StackLink, Toolbar, ToolbarFillSpace, ToolbarItem, @@ -12,8 +13,9 @@ import { useDataGridRemote, usePersistentColumnState, } from "@comet/admin"; -import { Block, Check, Delete } from "@comet/admin-icons"; +import { Block, Check, Delete, Edit } from "@comet/admin-icons"; import { ContentScopeInterface } from "@comet/cms-admin"; +import { IconButton } from "@mui/material"; import { DataGrid, GridColDef, GridToolbarQuickFilter } from "@mui/x-data-grid"; import * as React from "react"; import { FormattedMessage, IntlShape, useIntl } from "react-intl"; @@ -46,8 +48,8 @@ const deleteBrevoContactMutation = gql` `; const updateBrevoContactMutation = gql` - mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!) { - updateBrevoContact(id: $id, input: $input) { + mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!, $scope: EmailCampaignContentScopeInput!) { + updateBrevoContact(id: $id, input: $input, scope: $scope) { id } } @@ -137,39 +139,44 @@ export function BrevoContactsGrid({ type: "actions", renderCell: (params) => { return ( - + <> + + + - { - await client.mutate({ - mutation: updateBrevoContactMutation, - variables: { id: params.row.id, input: { blocked: !params.row.emailBlacklisted } }, - refetchQueries: [brevoContactsQuery], - }); - }} - icon={params.row.emailBlacklisted ? : } - > - {params.row.emailBlacklisted ? ( - - ) : ( - - )} - + + { + await client.mutate({ + mutation: updateBrevoContactMutation, + variables: { id: params.row.id, input: { blocked: !params.row.emailBlacklisted }, scope: scope }, + refetchQueries: [brevoContactsQuery], + }); + }} + icon={params.row.emailBlacklisted ? : } + > + {params.row.emailBlacklisted ? ( + + ) : ( + + )} + - { - await client.mutate({ - mutation: deleteBrevoContactMutation, - variables: { id: params.row.id }, - refetchQueries: [brevoContactsQuery], - }); - }} - icon={} - > - - + { + await client.mutate({ + mutation: deleteBrevoContactMutation, + variables: { id: params.row.id }, + refetchQueries: [brevoContactsQuery], + }); + }} + icon={} + > + + + - + ); }, }, diff --git a/packages/admin/src/brevoContacts/BrevoContactsPage.tsx b/packages/admin/src/brevoContacts/BrevoContactsPage.tsx index 6c7eed13..5bd8c933 100644 --- a/packages/admin/src/brevoContacts/BrevoContactsPage.tsx +++ b/packages/admin/src/brevoContacts/BrevoContactsPage.tsx @@ -1,18 +1,30 @@ +import { Stack, StackPage, StackSwitch } from "@comet/admin"; import { useContentScope } from "@comet/cms-admin"; import { GridColDef } from "@mui/x-data-grid"; import { DocumentNode } from "graphql"; import * as React from "react"; +import { useIntl } from "react-intl"; import { BrevoContactsGrid } from "./BrevoContactsGrid"; +import { BrevoContactForm, EditBrevoContactFormValues } from "./form/BrevoContactForm"; interface CreateContactsPageOptions { scopeParts: string[]; additionalAttributesFragment?: { name: string; fragment: DocumentNode }; additionalGridFields?: GridColDef[]; + additionalFormFields?: React.ReactNode; + input2State?: (values?: EditBrevoContactFormValues) => EditBrevoContactFormValues; } -function createBrevoContactsPage({ scopeParts, additionalAttributesFragment, additionalGridFields }: CreateContactsPageOptions) { +function createBrevoContactsPage({ + scopeParts, + additionalAttributesFragment, + additionalFormFields, + additionalGridFields, + input2State, +}: CreateContactsPageOptions) { function BrevoContactsPage(): JSX.Element { + const intl = useIntl(); const { scope: completeScope } = useContentScope(); const scope = scopeParts.reduce((acc, scopePart) => { @@ -21,11 +33,31 @@ function createBrevoContactsPage({ scopeParts, additionalAttributesFragment, add }, {} as { [key: string]: unknown }); return ( - + + + + + + + {(selectedId) => ( + + )} + + + ); } diff --git a/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts b/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts new file mode 100644 index 00000000..eedd21a9 --- /dev/null +++ b/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts @@ -0,0 +1,42 @@ +import { DocumentNode, gql } from "@apollo/client"; + +export const brevoContactFormQuery = (brevoContactFormFragment: DocumentNode) => gql` + query BrevoContactForm($id: Int!) { + brevoContact(id: $id) { + id + modifiedAt + ...BrevoContactForm + } + } + ${brevoContactFormFragment} +`; + +export const brevoContactFormCheckForChangesQuery = gql` + query BrevoContactFormCheckForChanges($id: Int!) { + brevoContact(id: $id) { + modifiedAt + } + } +`; + +export const createBrevoContactMutation = (brevoContactFormFragment: DocumentNode) => gql` + mutation CreateBrevoContact($scope: EmailCampaignContentScopeInput!, $input: BrevoContactUpdateInput!) { + createBrevoContact(scope: $scope, input: $input) { + id + modifiedAt + ...BrevoContactForm + } + } + ${brevoContactFormFragment} +`; + +export const updateBrevoContactMutation = (brevoContactFormFragment: DocumentNode) => gql` + mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!, $scope: EmailCampaignContentScopeInput!) { + updateBrevoContact(id: $id, input: $input, scope: $scope) { + id + modifiedAt + ...BrevoContactForm + } + } + ${brevoContactFormFragment} +`; diff --git a/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx b/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx new file mode 100644 index 00000000..13882da2 --- /dev/null +++ b/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx @@ -0,0 +1,193 @@ +import { DocumentNode, gql, useApolloClient, useQuery } from "@apollo/client"; +import { + FinalForm, + FinalFormSaveSplitButton, + FinalFormSubmitEvent, + FormSection, + Loading, + MainContent, + TextField, + Toolbar, + ToolbarActions, + ToolbarFillSpace, + ToolbarItem, + ToolbarTitleItem, + useFormApiRef, + useStackApi, + useStackSwitchApi, +} from "@comet/admin"; +import { ArrowLeft } from "@comet/admin-icons"; +import { ContentScopeInterface, EditPageLayout, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; +import { Card, IconButton } from "@mui/material"; +import { FormApi } from "final-form"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { GQLUpdateBrevoContactMutation, GQLUpdateBrevoContactMutationVariables } from "../BrevoContactsGrid.generated"; +import { + brevoContactFormCheckForChangesQuery, + brevoContactFormQuery, + createBrevoContactMutation, + updateBrevoContactMutation, +} from "./BrevoContactForm.gql"; +import { + GQLBrevoContactFormCheckForChangesQuery, + GQLBrevoContactFormCheckForChangesQueryVariables, + GQLBrevoContactFormQuery, + GQLBrevoContactFormQueryVariables, + GQLCreateBrevoContactMutation, + GQLCreateBrevoContactMutationVariables, +} from "./BrevoContactForm.gql.generated"; + +export type EditBrevoContactFormValues = { + email: string; + [key: string]: unknown; +}; + +interface FormProps { + id?: number; + scope: ContentScopeInterface; + additionalFormFields?: React.ReactNode; + additionalAttributesFragment?: { name: string; fragment: DocumentNode }; + input2State?: (values?: EditBrevoContactFormValues) => EditBrevoContactFormValues; +} + +export function BrevoContactForm({ id, scope, input2State, additionalFormFields, additionalAttributesFragment }: FormProps): React.ReactElement { + const stackApi = useStackApi(); + const client = useApolloClient(); + const mode = id ? "edit" : "add"; + const formApiRef = useFormApiRef(); + const stackSwitchApi = useStackSwitchApi(); + + const brevoContactFormFragment = gql` + fragment BrevoContactForm on BrevoContact { + email + createdAt + emailBlacklisted + smsBlacklisted + ${additionalAttributesFragment ? "...".concat(additionalAttributesFragment?.name) : ""} + } + ${additionalAttributesFragment?.fragment ?? ""} +`; + const { data, error, loading, refetch } = useQuery( + brevoContactFormQuery(brevoContactFormFragment), + id ? { variables: { id } } : { skip: true }, + ); + + const initialValues = React.useMemo>(() => { + let additionalInitialValues = {}; + + if (input2State) { + additionalInitialValues = input2State(data?.brevoContact); + } + return data?.brevoContact + ? { + email: data.brevoContact.email, + ...additionalInitialValues, + } + : additionalInitialValues; + }, [data?.brevoContact, input2State]); + + const saveConflict = useFormSaveConflict({ + checkConflict: async () => { + if (!id) { + return false; + } + const { data: updatedData } = await client.query< + GQLBrevoContactFormCheckForChangesQuery, + GQLBrevoContactFormCheckForChangesQueryVariables + >({ + query: brevoContactFormCheckForChangesQuery, + variables: { id }, + fetchPolicy: "no-cache", + }); + + return resolveHasSaveConflict(data?.brevoContact?.modifiedAt, updatedData.brevoContact.modifiedAt); + }, + formApiRef, + loadLatestVersion: async () => { + await refetch(); + }, + }); + + const handleSubmit = async (state: EditBrevoContactFormValues, form: FormApi, event: FinalFormSubmitEvent) => { + if (await saveConflict.checkForConflicts()) { + throw new Error("Conflicts detected"); + } + + const output = { + ...state, + blocked: false, + }; + + if (mode === "edit") { + if (!id) { + throw new Error("Missing id in edit mode"); + } + await client.mutate({ + mutation: updateBrevoContactMutation(brevoContactFormFragment), + variables: { id, input: output, scope }, + }); + } else { + const { data: mutationResponse } = await client.mutate({ + mutation: createBrevoContactMutation(brevoContactFormFragment), + variables: { scope, input: output }, + }); + if (!event.navigatingBack) { + const id = mutationResponse?.createBrevoContact?.id; + if (id) { + setTimeout(() => { + stackSwitchApi.activatePage("edit", id.toString()); + }); + } + } + } + }; + + if (error) throw error; + + if (loading) { + return ; + } + + return ( + apiRef={formApiRef} onSubmit={handleSubmit} mode={mode} initialValues={initialValues}> + {({ values }) => ( + + {saveConflict.dialogs} + + + + + + + + + + + + + + + + } + disabled={mode === "edit"} + /> + + {additionalFormFields && ( + + }> + {additionalFormFields} + + + )} + + + )} + + ); +} diff --git a/packages/admin/src/index.ts b/packages/admin/src/index.ts index 649ac487..f8ef0664 100644 --- a/packages/admin/src/index.ts +++ b/packages/admin/src/index.ts @@ -1,4 +1,5 @@ export { createBrevoContactsPage } from "./brevoContacts/BrevoContactsPage"; +export { EditBrevoContactFormValues } from "./brevoContacts/form/BrevoContactForm"; export { createEmailCampaignsPage } from "./emailCampaigns/EmailCampaignsPage"; export { EditTargetGroupFinalFormValues } from "./targetGroups/TargetGroupForm"; export { createTargetGroupsPage } from "./targetGroups/TargetGroupsPage"; From a762645fcaa249b143fba0c0c4d3bc226c7b1917 Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Mon, 10 Jun 2024 11:29:47 +0200 Subject: [PATCH 2/8] Add edit of contact to package api --- packages/api/generate-schema.ts | 10 +++- packages/api/schema.gql | 6 +- .../brevo-api/brevo-api-contact.service.ts | 13 +++- .../src/brevo-contact/brevo-contact.module.ts | 20 +++++-- .../brevo-contact/brevo-contact.resolver.ts | 46 +++++++++++++-- .../brevo-contact/brevo-contacts.service.ts | 26 +++++--- .../dto/brevo-contact-input.factory.ts | 59 +++++++++++++++++++ .../brevo-contact/dto/brevo-contact.input.ts | 9 --- packages/api/src/brevo-module.ts | 2 +- 9 files changed, 158 insertions(+), 33 deletions(-) create mode 100644 packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts delete mode 100644 packages/api/src/brevo-contact/dto/brevo-contact.input.ts diff --git a/packages/api/generate-schema.ts b/packages/api/generate-schema.ts index be784d76..1f753239 100644 --- a/packages/api/generate-schema.ts +++ b/packages/api/generate-schema.ts @@ -8,6 +8,7 @@ import { printSchema } from "graphql"; import { createBrevoContactResolver } from "./src/brevo-contact/brevo-contact.resolver"; import { BrevoContactFactory } from "./src/brevo-contact/dto/brevo-contact.factory"; +import { BrevoContactInputFactory } from "./src/brevo-contact/dto/brevo-contact-input.factory"; import { SubscribeInputFactory } from "./src/brevo-contact/dto/subscribe-input.factory"; import { EmailCampaignInputFactory } from "./src/email-campaign/dto/email-campaign-input.factory"; import { createEmailCampaignsResolver } from "./src/email-campaign/email-campaign.resolver"; @@ -59,8 +60,15 @@ async function generateSchema(): Promise { const gqlSchemaFactory = app.get(GraphQLSchemaFactory); const BrevoContact = BrevoContactFactory.create({}); + const [BrevoContactInput, BrevoContactUpdateInput] = BrevoContactInputFactory.create({}); const BrevoContactSubscribeInput = SubscribeInputFactory.create({ Scope: EmailCampaignScope }); - const BrevoContactResolver = createBrevoContactResolver({ BrevoContact, BrevoContactSubscribeInput, Scope: EmailCampaignScope }); + const BrevoContactResolver = createBrevoContactResolver({ + BrevoContact, + BrevoContactSubscribeInput, + Scope: EmailCampaignScope, + BrevoContactInput, + BrevoContactUpdateInput, + }); const TargetGroup = TargetGroupEntityFactory.create({ Scope: EmailCampaignScope }); const [TargetGroupInput, TargetGroupUpdateInput] = TargetGroupInputFactory.create({ BrevoFilterAttributes: BrevoContactFilterAttributes }); diff --git a/packages/api/schema.gql b/packages/api/schema.gql index 060eab10..c4f522a1 100644 --- a/packages/api/schema.gql +++ b/packages/api/schema.gql @@ -238,7 +238,8 @@ enum EmailCampaignSortField { } type Mutation { - updateBrevoContact(id: Int!, input: BrevoContactUpdateInput!): BrevoContact! + updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! + createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact deleteBrevoContact(id: Int!): Boolean! subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! createTargetGroup(scope: EmailCampaignContentScopeInput!, input: TargetGroupInput!): TargetGroup! @@ -252,7 +253,8 @@ type Mutation { } input BrevoContactUpdateInput { - blocked: Boolean! + email: String + blocked: Boolean } enum SubscribeResponse { diff --git a/packages/api/src/brevo-api/brevo-api-contact.service.ts b/packages/api/src/brevo-api/brevo-api-contact.service.ts index 67c3828d..fb7f12ea 100644 --- a/packages/api/src/brevo-api/brevo-api-contact.service.ts +++ b/packages/api/src/brevo-api/brevo-api-contact.service.ts @@ -3,7 +3,6 @@ import * as SibApiV3Sdk from "@sendinblue/client"; import { BrevoContactAttributesInterface } from "src/types"; import { BrevoContactInterface } from "../brevo-contact/dto/brevo-contact.factory"; -import { BrevoContactUpdateInput } from "../brevo-contact/dto/brevo-contact.input"; import { BrevoModuleConfig } from "../config/brevo-module.config"; import { BREVO_MODULE_CONFIG } from "../config/brevo-module.constants"; import { TargetGroupInputInterface } from "../target-group/dto/target-group-input.factory"; @@ -42,9 +41,17 @@ export class BrevoApiContactsService { return response.statusCode === 204 || response.statusCode === 201; } - public async updateContact(id: number, { blocked }: BrevoContactUpdateInput): Promise { + public async updateContact( + id: number, + { + blocked, + attributes, + listIds, + unlinkListIds, + }: { blocked?: boolean; attributes?: BrevoContactAttributesInterface; listIds?: number[]; unlinkListIds?: number[] }, + ): Promise { const idAsString = id.toString(); // brevo expects a string, because it can be an email or the id, so we have to transform the id to string - await this.contactsApi.updateContact(idAsString, { emailBlacklisted: blocked }); + await this.contactsApi.updateContact(idAsString, { emailBlacklisted: blocked, attributes, listIds, unlinkListIds }); return this.findContact(id); } diff --git a/packages/api/src/brevo-contact/brevo-contact.module.ts b/packages/api/src/brevo-contact/brevo-contact.module.ts index d7bdf388..1b833130 100644 --- a/packages/api/src/brevo-contact/brevo-contact.module.ts +++ b/packages/api/src/brevo-contact/brevo-contact.module.ts @@ -1,11 +1,14 @@ +import { MikroOrmModule } from "@mikro-orm/nestjs"; import { DynamicModule, Module, Type } from "@nestjs/common"; import { BrevoApiModule } from "../brevo-api/brevo-api.module"; import { ConfigModule } from "../config/config.module"; -import { BrevoContactAttributesInterface, BrevoContactFilterAttributesInterface, EmailCampaignScopeInterface } from "../types"; +import { TargetGroupInterface } from "../target-group/entity/target-group-entity.factory"; +import { BrevoContactAttributesInterface, EmailCampaignScopeInterface } from "../types"; import { createBrevoContactResolver } from "./brevo-contact.resolver"; import { BrevoContactsService } from "./brevo-contacts.service"; import { BrevoContactFactory } from "./dto/brevo-contact.factory"; +import { BrevoContactInputFactory } from "./dto/brevo-contact-input.factory"; import { SubscribeInputFactory } from "./dto/subscribe-input.factory"; import { EcgRtrListService } from "./ecg-rtr-list/ecg-rtr-list.service"; import { IsValidRedirectURLConstraint } from "./validator/redirect-url.validator"; @@ -13,19 +16,26 @@ import { IsValidRedirectURLConstraint } from "./validator/redirect-url.validator interface BrevoContactModuleConfig { BrevoContactAttributes?: Type; Scope: Type; - BrevoFilterAttributes?: Type; + TargetGroup: Type; } @Module({}) export class BrevoContactModule { - static register({ BrevoContactAttributes, Scope, BrevoFilterAttributes }: BrevoContactModuleConfig): DynamicModule { + static register({ BrevoContactAttributes, Scope, TargetGroup }: BrevoContactModuleConfig): DynamicModule { const BrevoContact = BrevoContactFactory.create({ BrevoContactAttributes }); const BrevoContactSubscribeInput = SubscribeInputFactory.create({ BrevoContactAttributes, Scope }); - const BrevoContactResolver = createBrevoContactResolver({ BrevoContact, BrevoContactSubscribeInput, Scope }); + const [BrevoContactInput, BrevoContactUpdateInput] = BrevoContactInputFactory.create({ BrevoContactAttributes }); + const BrevoContactResolver = createBrevoContactResolver({ + BrevoContact, + BrevoContactSubscribeInput, + Scope, + BrevoContactInput, + BrevoContactUpdateInput, + }); return { module: BrevoContactModule, - imports: [BrevoApiModule, ConfigModule], + imports: [BrevoApiModule, ConfigModule, MikroOrmModule.forFeature([TargetGroup])], providers: [BrevoContactsService, BrevoContactResolver, EcgRtrListService, IsValidRedirectURLConstraint], }; } diff --git a/packages/api/src/brevo-contact/brevo-contact.resolver.ts b/packages/api/src/brevo-contact/brevo-contact.resolver.ts index cb1cd09b..640eb185 100644 --- a/packages/api/src/brevo-contact/brevo-contact.resolver.ts +++ b/packages/api/src/brevo-contact/brevo-contact.resolver.ts @@ -1,5 +1,6 @@ import { AffectedEntity, PaginatedResponseFactory, RequiredPermission } from "@comet/cms-api"; -import { FilterQuery } from "@mikro-orm/core"; +import { EntityRepository, FilterQuery } from "@mikro-orm/core"; +import { InjectRepository } from "@mikro-orm/nestjs"; import { Inject, Type } from "@nestjs/common"; import { Args, ArgsType, Int, Mutation, ObjectType, Query, Resolver } from "@nestjs/graphql"; @@ -12,7 +13,7 @@ import { EmailCampaignScopeInterface } from "../types"; import { DynamicDtoValidationPipe } from "../validation/dynamic-dto-validation.pipe"; import { BrevoContactsService } from "./brevo-contacts.service"; import { BrevoContactInterface } from "./dto/brevo-contact.factory"; -import { BrevoContactUpdateInput } from "./dto/brevo-contact.input"; +import { BrevoContactInputInterface } from "./dto/brevo-contact-input.factory"; import { BrevoContactsArgsFactory } from "./dto/brevo-contacts.args"; import { SubscribeInputInterface } from "./dto/subscribe-input.factory"; import { SubscribeResponse } from "./dto/subscribe-response.enum"; @@ -22,9 +23,13 @@ export function createBrevoContactResolver({ BrevoContact, BrevoContactSubscribeInput, Scope, + BrevoContactInput, + BrevoContactUpdateInput, }: { BrevoContact: Type; BrevoContactSubscribeInput: Type; + BrevoContactInput: Type; + BrevoContactUpdateInput: Type>; Scope: Type; }): Type { @ObjectType() @@ -34,7 +39,7 @@ export function createBrevoContactResolver({ class BrevoContactsArgs extends BrevoContactsArgsFactory.create({ Scope }) {} @Resolver(() => BrevoContact) - @RequiredPermission(["brevo-newsletter"]) + @RequiredPermission(["brevo-newsletter"], { skipScopeCheck: true }) class BrevoContactResolver { constructor( @Inject(BREVO_MODULE_CONFIG) private readonly config: BrevoModuleConfig, @@ -42,6 +47,7 @@ export function createBrevoContactResolver({ private readonly brevoContactsService: BrevoContactsService, private readonly ecgRtrListService: EcgRtrListService, private readonly targetGroupService: TargetGroupsService, + @InjectRepository("TargetGroup") private readonly targetGroupRepository: EntityRepository, ) {} @Query(() => BrevoContact) @@ -88,9 +94,39 @@ export function createBrevoContactResolver({ @AffectedEntity(BrevoContact) async updateBrevoContact( @Args("id", { type: () => Int }) id: number, - @Args("input", { type: () => BrevoContactUpdateInput }) input: BrevoContactUpdateInput, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("input", { type: () => BrevoContactUpdateInput }) input: BrevoContactInputInterface, ): Promise { - return this.brevoContactsApiService.updateContact(id, input); + // Update attributes of contact before (un)assigning to target groups because they cannot be correctly validated for completeness + const contact = await this.brevoContactsApiService.updateContact(id, { + blocked: input.blocked, + attributes: input.attributes, + }); + + const assignedListIds = contact.listIds; + const mainListIds = (await this.targetGroupRepository.find({ brevoId: { $in: assignedListIds }, isMainList: true })).map( + (list) => list.brevoId, + ); + const updatedNonMainListIds = await this.brevoContactsService.getTargetGroupIdsForContact(scope, input.attributes ?? contact.attributes); + + // update contact again with updated list ids depending on new attributes + const contactWithUpdatedLists = await this.brevoContactsApiService.updateContact(id, { + listIds: updatedNonMainListIds.filter((listId) => !assignedListIds.includes(listId)), + unlinkListIds: assignedListIds.filter((listId) => !updatedNonMainListIds.includes(listId) && !mainListIds.includes(listId)), + }); + + return contactWithUpdatedLists; + } + + @Mutation(() => BrevoContact, { nullable: true }) + @RequiredPermission(["brevo-newsletter"], { skipScopeCheck: true }) + async createBrevoContact( + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("input", { type: () => BrevoContactUpdateInput }) + input: BrevoContactInputInterface, + ): Promise { + // TODO: add create brevo contact logic + return undefined; } @Mutation(() => Boolean) diff --git a/packages/api/src/brevo-contact/brevo-contacts.service.ts b/packages/api/src/brevo-contact/brevo-contacts.service.ts index d6d10066..bab60331 100644 --- a/packages/api/src/brevo-contact/brevo-contacts.service.ts +++ b/packages/api/src/brevo-contact/brevo-contacts.service.ts @@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common"; import { BrevoApiContactsService } from "../brevo-api/brevo-api-contact.service"; import { TargetGroupsService } from "../target-group/target-groups.service"; -import { EmailCampaignScopeInterface } from "../types"; +import { BrevoContactAttributesInterface, EmailCampaignScopeInterface } from "../types"; import { SubscribeInputInterface } from "./dto/subscribe-input.factory"; import { SubscribeResponse } from "./dto/subscribe-response.enum"; @@ -16,7 +16,23 @@ export class BrevoContactsService { templateId: number, ): Promise { const mainTargetGroupForScope = await this.targetGroupService.createIfNotExistMainTargetGroupForScope(scope); + const targetGroupIds = await this.getTargetGroupIdsForContact(scope, data.attributes); + const created = await this.brevoContactsApiService.createDoubleOptInBrevoContact( + data, + [mainTargetGroupForScope.brevoId, ...targetGroupIds], + templateId, + ); + if (created) { + return SubscribeResponse.SUCCESSFUL; + } + return SubscribeResponse.ERROR_UNKNOWN; + } + + public async getTargetGroupIdsForContact( + scope: EmailCampaignScopeInterface, + contactAttributes?: BrevoContactAttributesInterface, + ): Promise { let offset = 0; let totalCount = 0; const targetGroupIds: number[] = []; @@ -28,7 +44,7 @@ export class BrevoContactsService { offset += targetGroups.length; for (const targetGroup of targetGroups) { - const contactIsInTargetGroup = this.targetGroupService.checkIfContactIsInTargetGroup(data, targetGroup.filters); + const contactIsInTargetGroup = this.targetGroupService.checkIfContactIsInTargetGroup(contactAttributes, targetGroup.filters); if (contactIsInTargetGroup) { targetGroupIds.push(targetGroup.brevoId); @@ -36,10 +52,6 @@ export class BrevoContactsService { } } while (offset < totalCount); - const created = await this.brevoContactsApiService.createDoubleOptInBrevoContact(data, [mainTargetGroupForScope.brevoId], templateId); - if (created) { - return SubscribeResponse.SUCCESSFUL; - } - return SubscribeResponse.ERROR_UNKNOWN; + return targetGroupIds; } } diff --git a/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts b/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts new file mode 100644 index 00000000..f8202c26 --- /dev/null +++ b/packages/api/src/brevo-contact/dto/brevo-contact-input.factory.ts @@ -0,0 +1,59 @@ +import { IsUndefinable, PartialType } from "@comet/cms-api"; +import { Type } from "@nestjs/common"; +import { Field, InputType } from "@nestjs/graphql"; +import { Type as TypeTransformer } from "class-transformer"; +import { IsBoolean, IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator"; + +import { BrevoContactAttributesInterface } from "../../types"; + +export interface BrevoContactInputInterface { + email: string; + blocked?: boolean; + attributes?: BrevoContactAttributesInterface; +} + +export class BrevoContactInputFactory { + static create({ + BrevoContactAttributes, + }: { + BrevoContactAttributes?: Type; + }): [Type, Type>] { + @InputType({ + isAbstract: true, + }) + class BrevoContactInputBase implements BrevoContactInputInterface { + @IsNotEmpty() + @IsString() + @Field() + email: string; + + @IsBoolean() + @Field() + @IsOptional() + blocked?: boolean; + } + + if (BrevoContactAttributes) { + @InputType() + class BrevoContactInput extends BrevoContactInputBase { + @Field(() => BrevoContactAttributes, { nullable: true }) + @TypeTransformer(() => BrevoContactAttributes) + @ValidateNested() + @IsUndefinable() + attributes?: BrevoContactAttributesInterface; + } + @InputType() + class BrevoContactUpdateInput extends PartialType(BrevoContactInput) {} + + return [BrevoContactInput, BrevoContactUpdateInput]; + } + + @InputType() + class BrevoContactInput extends BrevoContactInputBase {} + + @InputType() + class BrevoContactUpdateInput extends PartialType(BrevoContactInput) {} + + return [BrevoContactInput, BrevoContactUpdateInput]; + } +} diff --git a/packages/api/src/brevo-contact/dto/brevo-contact.input.ts b/packages/api/src/brevo-contact/dto/brevo-contact.input.ts deleted file mode 100644 index fd73e102..00000000 --- a/packages/api/src/brevo-contact/dto/brevo-contact.input.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Field, InputType } from "@nestjs/graphql"; -import { IsBoolean } from "class-validator"; - -@InputType() -export class BrevoContactUpdateInput { - @Field(() => Boolean) - @IsBoolean() - blocked: boolean; -} diff --git a/packages/api/src/brevo-module.ts b/packages/api/src/brevo-module.ts index 36cab741..e4db2e66 100644 --- a/packages/api/src/brevo-module.ts +++ b/packages/api/src/brevo-module.ts @@ -23,8 +23,8 @@ export class BrevoModule { BrevoApiModule, BrevoContactModule.register({ BrevoContactAttributes: config.brevo.BrevoContactAttributes, - BrevoFilterAttributes: config.brevo.BrevoContactFilterAttributes, Scope: config.emailCampaigns.Scope, + TargetGroup, }), EmailCampaignModule.register({ EmailCampaignContentBlock: config.emailCampaigns.EmailCampaignContentBlock, From c5cee1e88ae4be6093fbf9ad731ec7ec70c1b33b Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Mon, 10 Jun 2024 11:30:52 +0200 Subject: [PATCH 3/8] Add contact edit to demo admin --- demo/admin/src/Routes.tsx | 2 + .../brevoContactsPageAttributesConfig.ts | 64 -------- .../brevoContactsPageAttributesConfig.tsx | 138 ++++++++++++++++++ demo/api/schema.gql | 7 +- 4 files changed, 145 insertions(+), 66 deletions(-) delete mode 100644 demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.ts create mode 100644 demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx diff --git a/demo/admin/src/Routes.tsx b/demo/admin/src/Routes.tsx index b6dda603..7ed9f0f4 100644 --- a/demo/admin/src/Routes.tsx +++ b/demo/admin/src/Routes.tsx @@ -31,6 +31,8 @@ export const Routes: React.FC = () => { scopeParts: ["domain", "language"], additionalAttributesFragment: brevoContactConfig.additionalAttributesFragment, additionalGridFields: brevoContactConfig.additionalGridFields, + additionalFormFields: brevoContactConfig.additionalFormFields, + input2State: brevoContactConfig.input2State, }); const TargetGroupsPage = createTargetGroupsPage({ diff --git a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.ts b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.ts deleted file mode 100644 index a41e5c89..00000000 --- a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { GridColDef } from "@mui/x-data-grid"; -import { DocumentNode } from "graphql"; -import gql from "graphql-tag"; -import { IntlShape } from "react-intl"; - -import { GQLBrevoContactAttributesFragmentFragment } from "./brevoContactsPageAttributesConfig.generated"; - -const attributesFragment = gql` - fragment BrevoContactAttributesFragment on BrevoContact { - attributes { - LASTNAME - FIRSTNAME - } - } -`; - -export const getBrevoContactConfig = ( - intl: IntlShape, -): { - additionalGridFields: GridColDef[]; - additionalAttributesFragment: { - fragment: DocumentNode; - name: string; - }; - exportFields: { - renderValue: (row: GQLBrevoContactAttributesFragmentFragment) => string; - headerName: string; - }[]; -} => { - return { - additionalGridFields: [ - { - field: "attributes.firstName", - headerName: intl.formatMessage({ id: "brevoContact.firstName", defaultMessage: "First name" }), - filterable: false, - sortable: false, - width: 150, - renderCell: ({ row }) => row.attributes?.FIRSTNAME, - }, - { - field: "attributes.lastName", - headerName: intl.formatMessage({ id: "brevoContact.lastName", defaultMessage: "Last name" }), - filterable: false, - sortable: false, - width: 150, - renderCell: ({ row }) => row.attributes?.LASTNAME, - }, - ], - additionalAttributesFragment: { - fragment: attributesFragment, - name: "BrevoContactAttributesFragment", - }, - exportFields: [ - { - renderValue: (row: GQLBrevoContactAttributesFragmentFragment) => row.attributes?.FIRSTNAME, - headerName: intl.formatMessage({ id: "brevoContact.firstName", defaultMessage: "First name" }), - }, - { - renderValue: (row: GQLBrevoContactAttributesFragmentFragment) => row.attributes?.LASTNAME, - headerName: intl.formatMessage({ id: "brevoContact.lastName", defaultMessage: "Last name" }), - }, - ], - }; -}; diff --git a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx new file mode 100644 index 00000000..32e3093f --- /dev/null +++ b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx @@ -0,0 +1,138 @@ +import { Field, FinalFormSelect, TextField } from "@comet/admin"; +import { EditBrevoContactFormValues } from "@comet/brevo-admin"; +import { MenuItem } from "@mui/material"; +import { GridColDef } from "@mui/x-data-grid"; +import { additionalPageTreeNodeFieldsFragment } from "@src/common/brevoModuleConfig/targetGroupFormConfig"; +import { GQLBrevoContactSalutation } from "@src/graphql.generated"; +import { DocumentNode } from "graphql"; +import gql from "graphql-tag"; +import React from "react"; +import { FormattedMessage, IntlShape } from "react-intl"; + +import { GQLBrevoContactAttributesFragmentFragment } from "./brevoContactsPageAttributesConfig.generated"; + +const attributesFragment = gql` + fragment BrevoContactAttributesFragment on BrevoContact { + attributes { + LASTNAME + FIRSTNAME + SALUTATION + } + } +`; + +const salutationOptions: Array<{ label: React.ReactNode; value: GQLBrevoContactSalutation }> = [ + { + label: , + value: "MALE", + }, + { + label: , + value: "FEMALE", + }, +]; + +interface AdditionalFormConfigInputProps extends EditBrevoContactFormValues { + attributes: { + SALUTATION?: GQLBrevoContactSalutation; + FIRSTNAME?: string; + LASTNAME?: string; + }; +} + +export const additionalFormConfig = { + nodeFragment: additionalPageTreeNodeFieldsFragment, +}; + +export const getBrevoContactConfig = ( + intl: IntlShape, +): { + additionalGridFields: GridColDef[]; + additionalFormFields: React.ReactNode; + additionalAttributesFragment: { + fragment: DocumentNode; + name: string; + }; + input2State: (values?: AdditionalFormConfigInputProps) => { + email: string; + attributes: { SALUTATION?: GQLBrevoContactSalutation; FIRSTNAME?: string; LASTNAME?: string }; + }; + exportFields: { + renderValue: (row: GQLBrevoContactAttributesFragmentFragment) => string; + headerName: string; + }[]; +} => { + return { + additionalGridFields: [ + { + field: "attributes.firstName", + headerName: intl.formatMessage({ id: "brevoContact.firstName", defaultMessage: "First name" }), + filterable: false, + sortable: false, + width: 150, + renderCell: ({ row }) => row.attributes?.FIRSTNAME, + }, + { + field: "attributes.lastName", + headerName: intl.formatMessage({ id: "brevoContact.lastName", defaultMessage: "Last name" }), + filterable: false, + sortable: false, + width: 150, + renderCell: ({ row }) => row.attributes?.LASTNAME, + }, + ], + additionalFormFields: ( + <> + } + name="attributes.SALUTATION" + fullWidth + > + {(props) => ( + + {salutationOptions.map((option) => ( + + {option.label} + + ))} + + )} + + } + name="attributes.FIRSTNAME" + fullWidth + /> + } + name="attributes.LASTNAME" + fullWidth + /> + + ), + input2State: (values?: AdditionalFormConfigInputProps) => { + return { + email: values?.email ?? "", + attributes: { + SALUTATION: values?.attributes?.SALUTATION, + FIRSTNAME: values?.attributes?.FIRSTNAME, + LASTNAME: values?.attributes?.LASTNAME, + }, + }; + }, + additionalAttributesFragment: { + fragment: attributesFragment, + name: "BrevoContactAttributesFragment", + }, + exportFields: [ + { + renderValue: (row: GQLBrevoContactAttributesFragmentFragment) => row.attributes?.FIRSTNAME, + headerName: intl.formatMessage({ id: "brevoContact.firstName", defaultMessage: "First name" }), + }, + { + renderValue: (row: GQLBrevoContactAttributesFragmentFragment) => row.attributes?.LASTNAME, + headerName: intl.formatMessage({ id: "brevoContact.lastName", defaultMessage: "Last name" }), + }, + ], + }; +}; diff --git a/demo/api/schema.gql b/demo/api/schema.gql index 2e10ebe9..e41da514 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -766,7 +766,8 @@ type Mutation { createProduct(input: ProductInput!): Product! updateProduct(id: ID!, input: ProductInput!, lastUpdatedAt: DateTime): Product! deleteProduct(id: ID!): Boolean! - updateBrevoContact(id: Int!, input: BrevoContactUpdateInput!): BrevoContact! + updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! + createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact deleteBrevoContact(id: Int!): Boolean! subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! createEmailCampaign(scope: EmailCampaignContentScopeInput!, input: EmailCampaignInput!): EmailCampaign! @@ -910,7 +911,9 @@ input ProductInput { } input BrevoContactUpdateInput { - blocked: Boolean! + email: String + blocked: Boolean + attributes: BrevoContactAttributesInput } enum SubscribeResponse { From 8c10df0e89829b792d5d1de1145613310edc23ba Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Mon, 10 Jun 2024 13:25:25 +0200 Subject: [PATCH 4/8] Remove scope from brevo contact edit due to affecting all scopes --- demo/api/schema.gql | 2 +- .../admin/src/brevoContacts/BrevoContactsGrid.tsx | 6 +++--- .../brevoContacts/form/BrevoContactForm.gql.ts | 4 ++-- .../src/brevoContacts/form/BrevoContactForm.tsx | 12 +++++++++++- packages/api/schema.gql | 2 +- .../src/brevo-contact/brevo-contact.resolver.ts | 8 +++++--- .../src/brevo-contact/brevo-contacts.service.ts | 15 +++++++++------ .../api/src/target-group/target-groups.service.ts | 12 ++++++++++-- 8 files changed, 42 insertions(+), 19 deletions(-) diff --git a/demo/api/schema.gql b/demo/api/schema.gql index e41da514..03eaf6c6 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -766,7 +766,7 @@ type Mutation { createProduct(input: ProductInput!): Product! updateProduct(id: ID!, input: ProductInput!, lastUpdatedAt: DateTime): Product! deleteProduct(id: ID!): Boolean! - updateBrevoContact(id: Int!, scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact! + updateBrevoContact(id: Int!, input: BrevoContactUpdateInput!): BrevoContact! createBrevoContact(scope: EmailCampaignContentScopeInput!, input: BrevoContactUpdateInput!): BrevoContact deleteBrevoContact(id: Int!): Boolean! subscribeBrevoContact(input: SubscribeInput!, scope: EmailCampaignContentScopeInput!): SubscribeResponse! diff --git a/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx b/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx index 9f7bf9b8..e377bebc 100644 --- a/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx +++ b/packages/admin/src/brevoContacts/BrevoContactsGrid.tsx @@ -48,8 +48,8 @@ const deleteBrevoContactMutation = gql` `; const updateBrevoContactMutation = gql` - mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!, $scope: EmailCampaignContentScopeInput!) { - updateBrevoContact(id: $id, input: $input, scope: $scope) { + mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!) { + updateBrevoContact(id: $id, input: $input) { id } } @@ -149,7 +149,7 @@ export function BrevoContactsGrid({ onClick={async () => { await client.mutate({ mutation: updateBrevoContactMutation, - variables: { id: params.row.id, input: { blocked: !params.row.emailBlacklisted }, scope: scope }, + variables: { id: params.row.id, input: { blocked: !params.row.emailBlacklisted } }, refetchQueries: [brevoContactsQuery], }); }} diff --git a/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts b/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts index eedd21a9..47c52239 100644 --- a/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts +++ b/packages/admin/src/brevoContacts/form/BrevoContactForm.gql.ts @@ -31,8 +31,8 @@ export const createBrevoContactMutation = (brevoContactFormFragment: DocumentNod `; export const updateBrevoContactMutation = (brevoContactFormFragment: DocumentNode) => gql` - mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!, $scope: EmailCampaignContentScopeInput!) { - updateBrevoContact(id: $id, input: $input, scope: $scope) { + mutation UpdateBrevoContact($id: Int!, $input: BrevoContactUpdateInput!) { + updateBrevoContact(id: $id, input: $input) { id modifiedAt ...BrevoContactForm diff --git a/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx b/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx index 13882da2..6bd3bb44 100644 --- a/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx +++ b/packages/admin/src/brevoContacts/form/BrevoContactForm.tsx @@ -1,5 +1,6 @@ import { DocumentNode, gql, useApolloClient, useQuery } from "@apollo/client"; import { + Alert, FinalForm, FinalFormSaveSplitButton, FinalFormSubmitEvent, @@ -19,6 +20,7 @@ import { import { ArrowLeft } from "@comet/admin-icons"; import { ContentScopeInterface, EditPageLayout, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; import { Card, IconButton } from "@mui/material"; +import { Box } from "@mui/system"; import { FormApi } from "final-form"; import React from "react"; import { FormattedMessage } from "react-intl"; @@ -126,7 +128,7 @@ export function BrevoContactForm({ id, scope, input2State, additionalFormFields, } await client.mutate({ mutation: updateBrevoContactMutation(brevoContactFormFragment), - variables: { id, input: output, scope }, + variables: { id, input: output }, }); } else { const { data: mutationResponse } = await client.mutate({ @@ -170,6 +172,14 @@ export function BrevoContactForm({ id, scope, input2State, additionalFormFields, + + + + + Int }) id: number, - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, @Args("input", { type: () => BrevoContactUpdateInput }) input: BrevoContactInputInterface, ): Promise { // Update attributes of contact before (un)assigning to target groups because they cannot be correctly validated for completeness @@ -105,9 +104,12 @@ export function createBrevoContactResolver({ const assignedListIds = contact.listIds; const mainListIds = (await this.targetGroupRepository.find({ brevoId: { $in: assignedListIds }, isMainList: true })).map( - (list) => list.brevoId, + (targetGroup) => targetGroup.brevoId, ); - const updatedNonMainListIds = await this.brevoContactsService.getTargetGroupIdsForContact(scope, input.attributes ?? contact.attributes); + + const updatedNonMainListIds = await this.brevoContactsService.getTargetGroupIdsForContact({ + contactAttributes: input.attributes ?? contact.attributes, + }); // update contact again with updated list ids depending on new attributes const contactWithUpdatedLists = await this.brevoContactsApiService.updateContact(id, { diff --git a/packages/api/src/brevo-contact/brevo-contacts.service.ts b/packages/api/src/brevo-contact/brevo-contacts.service.ts index bab60331..f6451151 100644 --- a/packages/api/src/brevo-contact/brevo-contacts.service.ts +++ b/packages/api/src/brevo-contact/brevo-contacts.service.ts @@ -16,7 +16,7 @@ export class BrevoContactsService { templateId: number, ): Promise { const mainTargetGroupForScope = await this.targetGroupService.createIfNotExistMainTargetGroupForScope(scope); - const targetGroupIds = await this.getTargetGroupIdsForContact(scope, data.attributes); + const targetGroupIds = await this.getTargetGroupIdsForContact({ scope, contactAttributes: data.attributes }); const created = await this.brevoContactsApiService.createDoubleOptInBrevoContact( data, @@ -29,17 +29,20 @@ export class BrevoContactsService { return SubscribeResponse.ERROR_UNKNOWN; } - public async getTargetGroupIdsForContact( - scope: EmailCampaignScopeInterface, - contactAttributes?: BrevoContactAttributesInterface, - ): Promise { + public async getTargetGroupIdsForContact({ + contactAttributes, + scope, + }: { + contactAttributes?: BrevoContactAttributesInterface; + scope?: EmailCampaignScopeInterface; + }): Promise { let offset = 0; let totalCount = 0; const targetGroupIds: number[] = []; const limit = 50; do { - const [targetGroups, totalContactLists] = await this.targetGroupService.findNonMainTargetGroups(scope, offset, limit); + const [targetGroups, totalContactLists] = await this.targetGroupService.findNonMainTargetGroups({ scope, offset, limit }); totalCount = totalContactLists; offset += targetGroups.length; diff --git a/packages/api/src/target-group/target-groups.service.ts b/packages/api/src/target-group/target-groups.service.ts index b977b373..22520bca 100644 --- a/packages/api/src/target-group/target-groups.service.ts +++ b/packages/api/src/target-group/target-groups.service.ts @@ -94,11 +94,19 @@ export class TargetGroupsService { return true; } - async findNonMainTargetGroups(scope: EmailCampaignScopeInterface, offset: number, limit: number): Promise<[TargetGroupInterface[], number]> { + async findNonMainTargetGroups({ + scope, + offset, + limit, + }: { + scope?: EmailCampaignScopeInterface; + offset: number; + limit: number; + }): Promise<[TargetGroupInterface[], number]> { const [targetGroups, totalContactLists] = await this.repository.findAndCount( { - scope, isMainList: false, + ...(scope ? { scope } : {}), }, { limit, offset }, ); From 42746b1be78aef4fef0d5f6f35ccabb712d875b7 Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Mon, 10 Jun 2024 13:50:48 +0200 Subject: [PATCH 5/8] Add changeset --- .changeset/slimy-zoos-explode.md | 5 +++++ .changeset/wet-zebras-relate.md | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 .changeset/slimy-zoos-explode.md create mode 100644 .changeset/wet-zebras-relate.md diff --git a/.changeset/slimy-zoos-explode.md b/.changeset/slimy-zoos-explode.md new file mode 100644 index 00000000..c99ef40d --- /dev/null +++ b/.changeset/slimy-zoos-explode.md @@ -0,0 +1,5 @@ +--- +"@comet/brevo-api": patch +--- + +Fix bug that does not add the contact to all target groups when subscribing diff --git a/.changeset/wet-zebras-relate.md b/.changeset/wet-zebras-relate.md new file mode 100644 index 00000000..06b6d5b9 --- /dev/null +++ b/.changeset/wet-zebras-relate.md @@ -0,0 +1,15 @@ +--- +"@comet/brevo-admin": minor +"@comet/brevo-api": minor +--- + +Add an edit page for brevo contacts +It is possible to configure additional form fields for this page in the `createBrevoContactsPage`. + +```diff + createBrevoContactsPage({ + //... ++ additionalFormFields: brevoContactConfig.additionalFormFields, ++ input2State: brevoContactConfig.input2State, + }); +``` From 615f404b8dd18997ee25fccf4be68ff45c72eeda Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Mon, 10 Jun 2024 13:53:39 +0200 Subject: [PATCH 6/8] Fix typo in comment --- packages/api/src/brevo-contact/brevo-contact.resolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/brevo-contact/brevo-contact.resolver.ts b/packages/api/src/brevo-contact/brevo-contact.resolver.ts index cedb9c69..0b9db76f 100644 --- a/packages/api/src/brevo-contact/brevo-contact.resolver.ts +++ b/packages/api/src/brevo-contact/brevo-contact.resolver.ts @@ -96,7 +96,7 @@ export function createBrevoContactResolver({ @Args("id", { type: () => Int }) id: number, @Args("input", { type: () => BrevoContactUpdateInput }) input: BrevoContactInputInterface, ): Promise { - // Update attributes of contact before (un)assigning to target groups because they cannot be correctly validated for completeness + // update attributes of contact before (un)assigning to target groups because they cannot be correctly validated for completeness const contact = await this.brevoContactsApiService.updateContact(id, { blocked: input.blocked, attributes: input.attributes, From e3e7156a9f4aeb96ad820258dd38f21b53c19133 Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Tue, 11 Jun 2024 14:24:08 +0200 Subject: [PATCH 7/8] Fix errors in message ids --- .../brevoModuleConfig/brevoContactsPageAttributesConfig.tsx | 6 +++--- .../src/common/brevoModuleConfig/targetGroupFormConfig.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx index 32e3093f..0dee9b15 100644 --- a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx +++ b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx @@ -2,7 +2,7 @@ import { Field, FinalFormSelect, TextField } from "@comet/admin"; import { EditBrevoContactFormValues } from "@comet/brevo-admin"; import { MenuItem } from "@mui/material"; import { GridColDef } from "@mui/x-data-grid"; -import { additionalPageTreeNodeFieldsFragment } from "@src/common/brevoModuleConfig/targetGroupFormConfig"; +import { additionalPageTreeNodeFieldsFragment } from "@src/common/brevoModuleConfig/brevoContactFormConfig"; import { GQLBrevoContactSalutation } from "@src/graphql.generated"; import { DocumentNode } from "graphql"; import gql from "graphql-tag"; @@ -23,11 +23,11 @@ const attributesFragment = gql` const salutationOptions: Array<{ label: React.ReactNode; value: GQLBrevoContactSalutation }> = [ { - label: , + label: , value: "MALE", }, { - label: , + label: , value: "FEMALE", }, ]; diff --git a/demo/admin/src/common/brevoModuleConfig/targetGroupFormConfig.tsx b/demo/admin/src/common/brevoModuleConfig/targetGroupFormConfig.tsx index 5f7edd7d..41afc6e1 100644 --- a/demo/admin/src/common/brevoModuleConfig/targetGroupFormConfig.tsx +++ b/demo/admin/src/common/brevoModuleConfig/targetGroupFormConfig.tsx @@ -8,11 +8,11 @@ import { FormattedMessage } from "react-intl"; const salutationOptions: Array<{ label: React.ReactNode; value: GQLBrevoContactSalutation }> = [ { - label: , + label: , value: "MALE", }, { - label: , + label: , value: "FEMALE", }, ]; From d4b9605781165a4e1c4c72d02fdc87bbbfb948ad Mon Sep 17 00:00:00 2001 From: Denise Buder Date: Tue, 11 Jun 2024 14:33:53 +0200 Subject: [PATCH 8/8] Fix wrong fragment in demo brevo contact config --- .../brevoModuleConfig/brevoContactsPageAttributesConfig.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx index 0dee9b15..a8451046 100644 --- a/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx +++ b/demo/admin/src/common/brevoModuleConfig/brevoContactsPageAttributesConfig.tsx @@ -2,7 +2,6 @@ import { Field, FinalFormSelect, TextField } from "@comet/admin"; import { EditBrevoContactFormValues } from "@comet/brevo-admin"; import { MenuItem } from "@mui/material"; import { GridColDef } from "@mui/x-data-grid"; -import { additionalPageTreeNodeFieldsFragment } from "@src/common/brevoModuleConfig/brevoContactFormConfig"; import { GQLBrevoContactSalutation } from "@src/graphql.generated"; import { DocumentNode } from "graphql"; import gql from "graphql-tag"; @@ -41,7 +40,7 @@ interface AdditionalFormConfigInputProps extends EditBrevoContactFormValues { } export const additionalFormConfig = { - nodeFragment: additionalPageTreeNodeFieldsFragment, + nodeFragment: attributesFragment, }; export const getBrevoContactConfig = (