diff --git a/apps/emails-and-messages/src/modules/mjml/configuration/get-mjml-configuration.service.ts b/apps/emails-and-messages/src/modules/mjml/configuration/get-mjml-configuration.service.ts index a8285d1..d8d6800 100644 --- a/apps/emails-and-messages/src/modules/mjml/configuration/get-mjml-configuration.service.ts +++ b/apps/emails-and-messages/src/modules/mjml/configuration/get-mjml-configuration.service.ts @@ -72,7 +72,7 @@ export class MjmlConfigurationService { return MjmlConfigContainer.getConfiguration(await this.getConfigurationRoot())({ id }); } - async getConfigurations(filter: FilterConfigurationsArgs) { + async getConfigurations(filter?: FilterConfigurationsArgs) { logger.debug("Get configuration"); return MjmlConfigContainer.getConfigurations(await this.getConfigurationRoot())(filter); } diff --git a/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-container.ts b/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-container.ts index 7b3e6c5..fbf6d58 100644 --- a/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-container.ts +++ b/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-container.ts @@ -50,19 +50,19 @@ export interface FilterConfigurationsArgs { const getConfigurations = (mjmlConfigRoot: MjmlConfigurationRoot | null | undefined) => - ({ ids, active }: FilterConfigurationsArgs): MjmlConfiguration[] => { + (filter: FilterConfigurationsArgs | undefined): MjmlConfiguration[] => { if (!mjmlConfigRoot || !mjmlConfigRoot.configurations) { return []; } let filtered = mjmlConfigRoot.configurations; - if (ids?.length) { - filtered = filtered.filter((c) => ids.includes(c.id)); + if (filter?.ids?.length) { + filtered = filtered.filter((c) => filter?.ids?.includes(c.id)); } - if (active !== undefined) { - filtered = filtered.filter((c) => c.active === active); + if (filter?.active !== undefined) { + filtered = filtered.filter((c) => c.active === filter.active); } return filtered; @@ -79,7 +79,7 @@ const createConfiguration = id: generateMjmlConfigurationId(), events: getDefaultEventsConfiguration(), }; - mjmlConfigNormalized.configurations.unshift(newConfiguration); + mjmlConfigNormalized.configurations.push(newConfiguration); return mjmlConfigNormalized; }; diff --git a/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-input-schema.ts b/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-input-schema.ts index 147b4ad..4b608ed 100644 --- a/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-input-schema.ts +++ b/apps/emails-and-messages/src/modules/mjml/configuration/mjml-config-input-schema.ts @@ -60,10 +60,12 @@ export const mjmlGetConfigurationInputSchema = z.object({ export const mjmlDeleteConfigurationInputSchema = z.object({ id: z.string(), }); -export const mjmlGetConfigurationsInputSchema = z.object({ - ids: z.array(z.string()).optional(), - active: z.boolean().optional(), -}); +export const mjmlGetConfigurationsInputSchema = z + .object({ + ids: z.array(z.string()).optional(), + active: z.boolean().optional(), + }) + .optional(); export const mjmlUpdateEventConfigurationInputSchema = z .object({ diff --git a/apps/emails-and-messages/src/modules/mjml/configuration/mjml-configuration.router.ts b/apps/emails-and-messages/src/modules/mjml/configuration/mjml-configuration.router.ts index 294cc61..aa45def 100644 --- a/apps/emails-and-messages/src/modules/mjml/configuration/mjml-configuration.router.ts +++ b/apps/emails-and-messages/src/modules/mjml/configuration/mjml-configuration.router.ts @@ -66,7 +66,13 @@ export const mjmlConfigurationRouter = router({ .mutation(async ({ ctx, input }) => { const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl }); logger.debug(input, "mjmlConfigurationRouter.delete called"); - + const existingConfiguration = await ctx.configurationService.getConfiguration(input); + if (!existingConfiguration) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Configuration not found", + }); + } await ctx.configurationService.deleteConfiguration(input); return null; }), diff --git a/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-form.tsx b/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-form.tsx index 4cc484b..483962a 100644 --- a/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-form.tsx +++ b/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-form.tsx @@ -5,8 +5,7 @@ import React, { useEffect } from "react"; import { MjmlConfiguration, smtpEncryptionTypes } from "../mjml-config"; import { trpcClient } from "../../../trpc/trpc-client"; import { useAppBridge, actions } from "@saleor/app-sdk/app-bridge"; -import { useRouter } from "next/router"; -import { mjmlUrls } from "../../urls"; +import { useQueryClient } from "@tanstack/react-query"; const useStyles = makeStyles((theme) => ({ field: { @@ -31,19 +30,40 @@ type Props = { export const MjmlConfigurationForm = (props: Props) => { const styles = useStyles(); - const router = useRouter(); const { appBridge } = useAppBridge(); const { handleSubmit, control, reset, setError } = useForm({ defaultValues: props.initialData, }); + const queryClient = useQueryClient(); + const { mutate: createOrUpdateConfiguration } = trpcClient.mjmlConfiguration.updateOrCreateConfiguration.useMutation({ - onSuccess(data, variables) { - router.replace(mjmlUrls.configuration(data.id)); - props.onConfigurationSaved(); + onSuccess: async (data, variables) => { + await queryClient.cancelQueries({ queryKey: ["mjmlConfiguration", "getConfigurations"] }); + // Optimistically update to the new value + queryClient.setQueryData>( + ["mjmlConfiguration", "getConfigurations", undefined], + (old) => { + if (old) { + const index = old.findIndex((c) => c.id === data.id); + // If thats an update, replace the old one + if (index !== -1) { + old[index] = data; + return [...old]; + } else { + return [...old, data]; + } + } else { + return [data]; + } + } + ); + + // Trigger refetch to make sure we have a fresh data + props.onConfigurationSaved(); appBridge?.dispatch( actions.Notification({ title: "Configuration saved", diff --git a/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-tab.tsx b/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-tab.tsx index f8a1c82..cc3ed6e 100644 --- a/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-tab.tsx +++ b/apps/emails-and-messages/src/modules/mjml/configuration/ui/mjml-configuration-tab.tsx @@ -12,6 +12,7 @@ import SideMenu from "../../../app-configuration/ui/side-menu"; import { MjmlConfiguration } from "../mjml-config"; import { LoadingIndicator } from "../../../ui/loading-indicator"; import { Add } from "@material-ui/icons"; +import { useQueryClient } from "@tanstack/react-query"; const useStyles = makeStyles((theme) => { return { @@ -50,31 +51,57 @@ export const MjmlConfigurationTab = ({ configurationId }: MjmlConfigurationTabPr const styles = useStyles(); const { appBridge } = useAppBridge(); const router = useRouter(); + const queryClient = useQueryClient(); const { data: configurations, refetch: refetchConfigurations, isLoading: configurationsIsLoading, - } = trpcClient.mjmlConfiguration.getConfigurations.useQuery( - {}, - { - onSuccess(data) { - if (!configurationId) { - navigateToFirstConfiguration(router, data); - } - }, - } - ); + isFetching: configurationsIsFetching, + isRefetching: configurationsIsRefetching, + } = trpcClient.mjmlConfiguration.getConfigurations.useQuery(undefined, { + onSuccess(data) { + if (!configurationId) { + console.log("no conf id! navigate to first"); + navigateToFirstConfiguration(router, data); + } + }, + }); - const { mutate: deleteConfiguration, error: deleteError } = + const { mutate: deleteConfiguration } = trpcClient.mjmlConfiguration.deleteConfiguration.useMutation({ - onSuccess(data, variables) { - refetchConfigurations(); + onError: (error) => { + appBridge?.dispatch( + actions.Notification({ + title: "Could not remove the configuration", + text: error.message, + status: "error", + }) + ); + }, + onSuccess: async (_data, variables) => { + await queryClient.cancelQueries({ queryKey: ["mjmlConfiguration", "getConfigurations"] }); + // remove value from the cache after the success + queryClient.setQueryData>( + ["mjmlConfiguration", "getConfigurations"], + (old) => { + if (old) { + const index = old.findIndex((c) => c.id === variables.id); + if (index !== -1) { + delete old[index]; + return [...old]; + } + } + } + ); + // if we just deleted the configuration that was selected - // we have to navigate to the first configuration + // we have to update the URL if (variables.id === configurationId) { - navigateToFirstConfiguration(router, configurations); + router.replace(mjmlUrls.configuration()); } + + refetchConfigurations(); appBridge?.dispatch( actions.Notification({ title: "Success", @@ -85,7 +112,7 @@ export const MjmlConfigurationTab = ({ configurationId }: MjmlConfigurationTabPr }, }); - if (configurationsIsLoading) { + if (configurationsIsLoading || configurationsIsFetching) { return ; } @@ -117,7 +144,7 @@ export const MjmlConfigurationTab = ({ configurationId }: MjmlConfigurationTabPr items={configurations?.map((c) => ({ label: c.configurationName, id: c.id })) || []} />
- {configurationsIsLoading ? ( + {configurationsIsLoading || configurationsIsFetching ? ( ) : ( <>