📧 Improve SMTP event section (#548)
* Make channels section expandable based on override setting * Revert "Make channels section expandable based on override setting" This reverts commit e107c5e990b4110156043ed494fb0054bd936654. * Improve copy in the descriptions * Handle partial updates i n events * Add status component * Fix typos and types * Improve SMTP events section * Add changeset * Implement event sections as tables with array form * Update the changelog * Remove no longer used component * Add empty option for template choice * Remove no longer used component * Update the test
This commit is contained in:
parent
f96563fc2b
commit
8287075e29
17 changed files with 339 additions and 229 deletions
5
.changeset/young-tigers-provide.md
Normal file
5
.changeset/young-tigers-provide.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-emails-and-messages": patch
|
||||
---
|
||||
|
||||
Events section UI has been updated. All events are displayed now as single table.
|
12
apps/emails-and-messages/src/components/table.tsx
Normal file
12
apps/emails-and-messages/src/components/table.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
||||
|
||||
export const Table = {
|
||||
Container: (props: BoxProps) => <Box __textAlign={"left"} width="100%" {...props} as="table" />,
|
||||
Header: (props: BoxProps) => <Box {...props} as="thead" />,
|
||||
Row: (props: BoxProps) => <Box {...props} as="tr" />,
|
||||
HeaderCell: (props: BoxProps) => (
|
||||
<Box fontWeight={"captionSmall"} fontSize={"captionSmall"} {...props} as="th" />
|
||||
),
|
||||
Body: (props: BoxProps) => <Box {...props} as="tbody" />,
|
||||
Cell: (props: BoxProps) => <Box fontSize="bodyMedium" {...props} as="td" />,
|
||||
};
|
|
@ -2,7 +2,8 @@ import { Text } from "@saleor/macaw-ui/next";
|
|||
|
||||
export const ConfigurationNameDescriptionText = () => (
|
||||
<Text as="p">
|
||||
The name for your configuration. You can have more than one if you want to use different settings for each channel.
|
||||
The name for your configuration. You can have more than one if you want to use different
|
||||
settings for each channel.
|
||||
<br />
|
||||
For example - <code>production</code> and <code>development</code>.
|
||||
</Text>
|
||||
|
|
|
@ -22,7 +22,7 @@ export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
|||
ORDER_FULLY_PAID: "Order fully paid",
|
||||
INVOICE_SENT: "Invoice sent",
|
||||
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
||||
ACCOUNT_PASSWORD_RESET: "Customer account password reset",
|
||||
ACCOUNT_PASSWORD_RESET: "Customer account password reset request",
|
||||
ACCOUNT_CHANGE_EMAIL_REQUEST: "Customer account change email request",
|
||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Customer account change email confirmation",
|
||||
ACCOUNT_DELETE: "Customer account delete request",
|
||||
|
|
|
@ -86,3 +86,10 @@ export const sendgridUpdateEventSchema = sendgridConfigurationEventSchema.merge(
|
|||
);
|
||||
|
||||
export type SendgridUpdateEvent = z.infer<typeof sendgridUpdateEventSchema>;
|
||||
|
||||
export const sendgridUpdateEventArraySchema = z.object({
|
||||
configurationId: z.string(),
|
||||
events: z.array(sendgridConfigurationEventSchema),
|
||||
});
|
||||
|
||||
export type SendgridUpdateEventArray = z.infer<typeof sendgridUpdateEventArraySchema>;
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
sendgridGetEventConfigurationInputSchema,
|
||||
sendgridUpdateApiConnectionSchema,
|
||||
sendgridUpdateBasicInformationSchema,
|
||||
sendgridUpdateEventConfigurationInputSchema,
|
||||
sendgridUpdateEventArraySchema,
|
||||
sendgridUpdateEventSchema,
|
||||
sendgridUpdateSenderSchema,
|
||||
} from "./sendgrid-config-input-schema";
|
||||
|
@ -147,23 +147,6 @@ export const sendgridConfigurationRouter = router({
|
|||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
updateEventConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(sendgridUpdateEventConfigurationInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "sendgridConfigurationRouter.updateEventConfiguration called");
|
||||
|
||||
try {
|
||||
return await ctx.sendgridConfigurationService.updateEventConfiguration({
|
||||
configurationId: input.configurationId,
|
||||
eventConfiguration: input,
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
updateBasicInformation: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(sendgridUpdateBasicInformationSchema)
|
||||
|
@ -260,13 +243,41 @@ export const sendgridConfigurationRouter = router({
|
|||
|
||||
logger.debug(input, "sendgridConfigurationRouter.updateEvent called");
|
||||
|
||||
const { id: configurationId, eventType, ...eventConfiguration } = input;
|
||||
|
||||
try {
|
||||
return await ctx.sendgridConfigurationService.updateEventConfiguration({
|
||||
eventConfiguration: input,
|
||||
configurationId: input.id,
|
||||
configurationId,
|
||||
eventType,
|
||||
eventConfiguration,
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
|
||||
updateEventArray: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(sendgridUpdateEventArraySchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "sendgridConfigurationRouter.updateEventArray called");
|
||||
|
||||
return await Promise.all(
|
||||
input.events.map(async (event) => {
|
||||
const { eventType, ...eventConfiguration } = event;
|
||||
|
||||
try {
|
||||
return await ctx.sendgridConfigurationService.updateEventConfiguration({
|
||||
configurationId: input.configurationId,
|
||||
eventType,
|
||||
eventConfiguration,
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -512,6 +512,7 @@ describe("SendgridConfigurationService", function () {
|
|||
|
||||
await service.updateEventConfiguration({
|
||||
configurationId: validConfig.configurations[0].id,
|
||||
eventType: validConfig.configurations[0].events[0].eventType,
|
||||
eventConfiguration: {
|
||||
...validConfig.configurations[0].events[0],
|
||||
template: "42",
|
||||
|
@ -544,6 +545,7 @@ describe("SendgridConfigurationService", function () {
|
|||
await expect(async () =>
|
||||
service.updateEventConfiguration({
|
||||
configurationId: "this-id-does-not-exist",
|
||||
eventType: validConfig.configurations[0].events[0].eventType,
|
||||
eventConfiguration: {
|
||||
...validConfig.configurations[0].events[0],
|
||||
template: "42",
|
||||
|
|
|
@ -234,19 +234,19 @@ export class SendgridConfigurationService {
|
|||
*/
|
||||
async updateEventConfiguration({
|
||||
configurationId,
|
||||
eventType,
|
||||
eventConfiguration,
|
||||
}: {
|
||||
configurationId: string;
|
||||
eventConfiguration: SendgridEventConfiguration;
|
||||
eventType: SendgridEventConfiguration["eventType"];
|
||||
eventConfiguration: Partial<Omit<SendgridEventConfiguration, "eventType">>;
|
||||
}) {
|
||||
logger.debug("Update event configuration");
|
||||
const configuration = await this.getConfiguration({
|
||||
id: configurationId,
|
||||
});
|
||||
|
||||
const eventIndex = configuration.events.findIndex(
|
||||
(e) => e.eventType === eventConfiguration.eventType
|
||||
);
|
||||
const eventIndex = configuration.events.findIndex((e) => e.eventType === eventType);
|
||||
|
||||
if (eventIndex < 0) {
|
||||
logger.warn("Event configuration not found, throwing an error");
|
||||
|
@ -256,9 +256,14 @@ export class SendgridConfigurationService {
|
|||
);
|
||||
}
|
||||
|
||||
configuration.events[eventIndex] = eventConfiguration;
|
||||
const updatedEventConfiguration = {
|
||||
...configuration.events[eventIndex],
|
||||
...eventConfiguration,
|
||||
};
|
||||
|
||||
configuration.events[eventIndex] = updatedEventConfiguration;
|
||||
|
||||
await this.updateConfiguration(configuration);
|
||||
return configuration;
|
||||
return updatedEventConfiguration;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import { defaultPadding } from "../../../components/ui-defaults";
|
|||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import {
|
||||
SendgridUpdateEvent,
|
||||
sendgridUpdateEventSchema,
|
||||
SendgridUpdateEventArray,
|
||||
sendgridUpdateEventArraySchema,
|
||||
} from "../configuration/sendgrid-config-input-schema";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { BoxFooter } from "../../../components/box-footer";
|
||||
|
@ -18,91 +18,57 @@ import { useQuery } from "@tanstack/react-query";
|
|||
import { fetchTemplates } from "../sendgrid-api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
||||
import { Combobox } from "@saleor/react-hook-form-macaw";
|
||||
import { Select } from "@saleor/react-hook-form-macaw";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
|
||||
interface EventBoxProps {
|
||||
configuration: SendgridConfiguration;
|
||||
event: SendgridEventConfiguration;
|
||||
}
|
||||
|
||||
const EventBox = ({ event, configuration }: EventBoxProps) => {
|
||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
|
||||
const { data: templatesChoices } = useQuery({
|
||||
queryKey: ["sendgridTemplates"],
|
||||
queryFn: fetchTemplates({ apiKey: configuration.apiKey }),
|
||||
enabled: !!configuration.apiKey?.length,
|
||||
});
|
||||
|
||||
const { handleSubmit, control, setError, register } = useForm<SendgridUpdateEvent>({
|
||||
defaultValues: {
|
||||
id: configuration.id,
|
||||
...event,
|
||||
},
|
||||
resolver: zodResolver(sendgridUpdateEventSchema),
|
||||
});
|
||||
|
||||
const trpcContext = trpcClient.useContext();
|
||||
const { mutate } = trpcClient.sendgridConfiguration.updateEvent.useMutation({
|
||||
onSuccess: async () => {
|
||||
notifySuccess("Configuration saved");
|
||||
trpcContext.sendgridConfiguration.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
setBackendErrors<SendgridUpdateEvent>({ error, setError, notifyError });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit((data, event) => {
|
||||
mutate({
|
||||
...data,
|
||||
});
|
||||
})}
|
||||
>
|
||||
<BoxWithBorder>
|
||||
<Box padding={defaultPadding} display="flex" flexDirection="column" gap={defaultPadding}>
|
||||
<Text variant="heading">{event.eventType}</Text>
|
||||
{templatesChoices?.length ? (
|
||||
<Combobox
|
||||
name="template"
|
||||
control={control}
|
||||
label="Template"
|
||||
options={templatesChoices.map((sender) => ({
|
||||
label: sender.label,
|
||||
value: sender.value,
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<Combobox name="template" control={control} label="Template" options={[]} />
|
||||
)}
|
||||
|
||||
<label>
|
||||
<input type="checkbox" placeholder="Enabled" {...register("active")} />
|
||||
<Text paddingLeft={defaultPadding}>Active</Text>
|
||||
</label>
|
||||
</Box>
|
||||
<BoxFooter>
|
||||
<Button type="submit">Save event</Button>
|
||||
</BoxFooter>
|
||||
</BoxWithBorder>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
import { messageEventTypesLabels } from "../../event-handlers/message-event-types";
|
||||
import { Table } from "../../../components/table";
|
||||
|
||||
interface SendgridEventsSectionProps {
|
||||
configuration: SendgridConfiguration;
|
||||
}
|
||||
|
||||
export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionProps) => {
|
||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
|
||||
// Sort events by displayed label
|
||||
const eventsSorted = configuration.events.sort((a, b) =>
|
||||
messageEventTypesLabels[a.eventType].localeCompare(messageEventTypesLabels[b.eventType])
|
||||
);
|
||||
|
||||
const { control, register, handleSubmit, setError } = useForm<SendgridUpdateEventArray>({
|
||||
defaultValues: {
|
||||
configurationId: configuration.id,
|
||||
events: eventsSorted,
|
||||
},
|
||||
resolver: zodResolver(sendgridUpdateEventArraySchema),
|
||||
});
|
||||
|
||||
const trpcContext = trpcClient.useContext();
|
||||
const { mutate } = trpcClient.sendgridConfiguration.updateEventArray.useMutation({
|
||||
onSuccess: async () => {
|
||||
notifySuccess("Configuration saved");
|
||||
trpcContext.sendgridConfiguration.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
setBackendErrors<SendgridUpdateEventArray>({ error, setError, notifyError });
|
||||
},
|
||||
});
|
||||
|
||||
const { data: sendgridTemplates } = useQuery({
|
||||
queryKey: ["sendgridTemplates"],
|
||||
queryFn: fetchTemplates({ apiKey: configuration.apiKey }),
|
||||
enabled: !!configuration.apiKey?.length,
|
||||
});
|
||||
|
||||
const templateChoices = [{ value: "", label: "----" }, ...(sendgridTemplates || [])];
|
||||
|
||||
return (
|
||||
<SectionWithDescription
|
||||
title="Events"
|
||||
description={
|
||||
<Text as="p">
|
||||
Choose which Saleor events should send emails via Sendgrid. You can create and modify your templates in the
|
||||
Choose which Saleor events should send emails via Sendgrid. You can create and modify your
|
||||
templates in the
|
||||
<TextLink href="https://mc.sendgrid.com/dynamic-templates" newTab={true}>
|
||||
Sendgrid dashboard
|
||||
</TextLink>
|
||||
|
@ -110,11 +76,47 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
|||
</Text>
|
||||
}
|
||||
>
|
||||
<Box display="flex" flexDirection="column" gap={defaultPadding}>
|
||||
{configuration.events.map((event) => (
|
||||
<EventBox key={event.eventType} configuration={configuration} event={event} />
|
||||
))}
|
||||
</Box>
|
||||
<form
|
||||
onSubmit={handleSubmit((data) => {
|
||||
mutate(data);
|
||||
})}
|
||||
>
|
||||
<BoxWithBorder>
|
||||
<Box padding={defaultPadding}>
|
||||
<Table.Container>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell __width={40}>Active</Table.HeaderCell>
|
||||
<Table.HeaderCell>Event type</Table.HeaderCell>
|
||||
<Table.HeaderCell>Dynamic template</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{eventsSorted.map((event, index) => (
|
||||
<Table.Row key={event.eventType}>
|
||||
<Table.Cell>
|
||||
<input type="checkbox" {...register(`events.${index}.active`)} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Text>{messageEventTypesLabels[event.eventType]}</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Select
|
||||
control={control}
|
||||
name={`events.${index}.template`}
|
||||
options={templateChoices}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Container>
|
||||
</Box>
|
||||
<BoxFooter>
|
||||
<Button type="submit">Save provider</Button>
|
||||
</BoxFooter>
|
||||
</BoxWithBorder>
|
||||
</form>
|
||||
</SectionWithDescription>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -72,6 +72,21 @@ export const smtpUpdateEventSchema = smtpConfigurationEventSchema.merge(
|
|||
|
||||
export type SmtpUpdateEvent = z.infer<typeof smtpUpdateEventSchema>;
|
||||
|
||||
export const smtpUpdateEventActiveStatusInputSchema = smtpConfigurationEventSchema
|
||||
.pick({
|
||||
active: true,
|
||||
eventType: true,
|
||||
})
|
||||
.merge(
|
||||
smtpConfigurationSchema.pick({
|
||||
id: true,
|
||||
})
|
||||
);
|
||||
|
||||
export type SmtpUpdateEventActiveStatusInput = z.infer<
|
||||
typeof smtpUpdateEventActiveStatusInputSchema
|
||||
>;
|
||||
|
||||
export const smtpGetEventConfigurationInputSchema = smtpConfigurationIdInputSchema.merge(
|
||||
z.object({
|
||||
eventType: z.enum(messageEventTypes),
|
||||
|
@ -87,3 +102,10 @@ export const smtpUpdateEventConfigurationInputSchema = smtpConfigurationIdInputS
|
|||
export type SmtpUpdateEventConfigurationInput = z.infer<
|
||||
typeof smtpUpdateEventConfigurationInputSchema
|
||||
>;
|
||||
|
||||
export const smtpUpdateEventArraySchema = z.object({
|
||||
configurationId: z.string(),
|
||||
events: z.array(smtpConfigurationEventSchema),
|
||||
});
|
||||
|
||||
export type SmtpUpdateEventArray = z.infer<typeof smtpUpdateEventArraySchema>;
|
||||
|
|
|
@ -15,7 +15,8 @@ import {
|
|||
smtpGetConfigurationsInputSchema,
|
||||
smtpGetEventConfigurationInputSchema,
|
||||
smtpUpdateBasicInformationSchema,
|
||||
smtpUpdateEventConfigurationInputSchema,
|
||||
smtpUpdateEventActiveStatusInputSchema,
|
||||
smtpUpdateEventArraySchema,
|
||||
smtpUpdateEventSchema,
|
||||
smtpUpdateSenderSchema,
|
||||
smtpUpdateSmtpSchema,
|
||||
|
@ -150,23 +151,6 @@ export const smtpConfigurationRouter = router({
|
|||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
updateEventConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(smtpUpdateEventConfigurationInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "mjmlConfigurationRouter.updateEventConfiguration or create called");
|
||||
|
||||
try {
|
||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
||||
configurationId: input.id,
|
||||
eventConfiguration: input,
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
|
||||
renderTemplate: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
|
@ -287,13 +271,59 @@ export const smtpConfigurationRouter = router({
|
|||
|
||||
logger.debug(input, "smtpConfigurationRouter.updateEvent called");
|
||||
|
||||
const { id: configurationId, eventType, ...eventConfiguration } = input;
|
||||
|
||||
try {
|
||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
||||
eventConfiguration: input,
|
||||
configurationId: input.id,
|
||||
configurationId,
|
||||
eventType,
|
||||
eventConfiguration,
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
updateEventActiveStatus: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(smtpUpdateEventActiveStatusInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "mjmlConfigurationRouter.updateEventActiveStatus called");
|
||||
try {
|
||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
||||
configurationId: input.id,
|
||||
eventType: input.eventType,
|
||||
eventConfiguration: {
|
||||
active: input.active,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
}),
|
||||
updateEventArray: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(smtpUpdateEventArraySchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "smtpConfigurationRouter.updateEventArray called");
|
||||
|
||||
return await Promise.all(
|
||||
input.events.map(async (event) => {
|
||||
const { eventType, ...eventConfiguration } = event;
|
||||
|
||||
try {
|
||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
||||
configurationId: input.configurationId,
|
||||
eventType,
|
||||
eventConfiguration,
|
||||
});
|
||||
} catch (e) {
|
||||
throwTrpcErrorFromConfigurationServiceError(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -539,6 +539,7 @@ describe("SmtpConfigurationService", function () {
|
|||
|
||||
await service.updateEventConfiguration({
|
||||
configurationId: validConfig.configurations[0].id,
|
||||
eventType: validConfig.configurations[0].events[0].eventType,
|
||||
eventConfiguration: {
|
||||
...validConfig.configurations[0].events[0],
|
||||
subject: "Updated subject",
|
||||
|
@ -571,6 +572,7 @@ describe("SmtpConfigurationService", function () {
|
|||
await expect(async () =>
|
||||
service.updateEventConfiguration({
|
||||
configurationId: "this-id-does-not-exist",
|
||||
eventType: validConfig.configurations[0].events[0].eventType,
|
||||
eventConfiguration: {
|
||||
...validConfig.configurations[0].events[0],
|
||||
subject: "Updated subject",
|
||||
|
|
|
@ -225,18 +225,18 @@ export class SmtpConfigurationService {
|
|||
async updateEventConfiguration({
|
||||
configurationId,
|
||||
eventConfiguration,
|
||||
eventType,
|
||||
}: {
|
||||
configurationId: string;
|
||||
eventConfiguration: SmtpEventConfiguration;
|
||||
eventType: SmtpEventConfiguration["eventType"];
|
||||
eventConfiguration: Partial<Omit<SmtpEventConfiguration, "eventType">>;
|
||||
}) {
|
||||
logger.debug("Update event configuration");
|
||||
const configuration = await this.getConfiguration({
|
||||
id: configurationId,
|
||||
});
|
||||
|
||||
const eventIndex = configuration.events.findIndex(
|
||||
(e) => e.eventType === eventConfiguration.eventType
|
||||
);
|
||||
const eventIndex = configuration.events.findIndex((e) => e.eventType === eventType);
|
||||
|
||||
if (eventIndex < 0) {
|
||||
logger.warn("Event configuration not found, throwing an error");
|
||||
|
@ -246,9 +246,14 @@ export class SmtpConfigurationService {
|
|||
);
|
||||
}
|
||||
|
||||
configuration.events[eventIndex] = eventConfiguration;
|
||||
const updatedEventConfiguration = {
|
||||
...configuration.events[eventIndex],
|
||||
...eventConfiguration,
|
||||
};
|
||||
|
||||
configuration.events[eventIndex] = updatedEventConfiguration;
|
||||
|
||||
await this.updateConfiguration(configuration);
|
||||
return configuration;
|
||||
return updatedEventConfiguration;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,8 @@ import { MessageEventTypes } from "../../event-handlers/message-event-types";
|
|||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
SmtpUpdateEventConfigurationInput,
|
||||
smtpUpdateEventConfigurationInputSchema,
|
||||
} from "../configuration/smtp-config-input-schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { CodeEditor } from "./code-edtor";
|
||||
import { CodeEditor } from "./code-editor";
|
||||
import { useDebounce } from "usehooks-ts";
|
||||
import { useState, useEffect } from "react";
|
||||
import { examplePayloads } from "../../event-handlers/default-payloads";
|
||||
|
@ -17,6 +13,7 @@ import { MjmlPreview } from "./mjml-preview";
|
|||
import { defaultPadding } from "../../../components/ui-defaults";
|
||||
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
||||
import { Input } from "@saleor/react-hook-form-macaw";
|
||||
import { SmtpUpdateEvent, smtpUpdateEventSchema } from "../configuration/smtp-config-input-schema";
|
||||
const PREVIEW_DEBOUNCE_DELAY = 500;
|
||||
|
||||
interface EventFormProps {
|
||||
|
@ -31,15 +28,13 @@ export const EventForm = ({ configuration, eventType }: EventFormProps) => {
|
|||
(eventConfiguration) => eventConfiguration.eventType === eventType
|
||||
)!; // Event conf is not optional, so we can use ! here
|
||||
|
||||
const { handleSubmit, control, getValues, setError } = useForm<SmtpUpdateEventConfigurationInput>(
|
||||
{
|
||||
defaultValues: {
|
||||
id: configuration.id,
|
||||
...eventConfiguration,
|
||||
},
|
||||
resolver: zodResolver(smtpUpdateEventConfigurationInputSchema),
|
||||
}
|
||||
);
|
||||
const { handleSubmit, control, getValues, setError } = useForm<SmtpUpdateEvent>({
|
||||
defaultValues: {
|
||||
id: configuration.id,
|
||||
...eventConfiguration,
|
||||
},
|
||||
resolver: zodResolver(smtpUpdateEventSchema),
|
||||
});
|
||||
|
||||
const trpcContext = trpcClient.useContext();
|
||||
const { mutate } = trpcClient.smtpConfiguration.updateEvent.useMutation({
|
||||
|
@ -48,7 +43,7 @@ export const EventForm = ({ configuration, eventType }: EventFormProps) => {
|
|||
trpcContext.smtpConfiguration.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
setBackendErrors<SmtpUpdateEventConfigurationInput>({
|
||||
setBackendErrors<SmtpUpdateEvent>({
|
||||
error,
|
||||
setError,
|
||||
notifyError,
|
||||
|
|
|
@ -1,89 +1,57 @@
|
|||
import { SmtpConfiguration, SmtpEventConfiguration } from "../configuration/smtp-config-schema";
|
||||
import { SmtpConfiguration } from "../configuration/smtp-config-schema";
|
||||
import { BoxWithBorder } from "../../../components/box-with-border";
|
||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import { defaultPadding } from "../../../components/ui-defaults";
|
||||
import { SectionWithDescription } from "../../../components/section-with-description";
|
||||
import { useRouter } from "next/router";
|
||||
import { smtpUrls } from "../urls";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import React from "react";
|
||||
import { messageEventTypesLabels } from "../../event-handlers/message-event-types";
|
||||
import { BoxFooter } from "../../../components/box-footer";
|
||||
import { Table } from "../../../components/table";
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import {
|
||||
SmtpUpdateEventArray,
|
||||
smtpUpdateEventArraySchema,
|
||||
} from "../configuration/smtp-config-input-schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { BoxFooter } from "../../../components/box-footer";
|
||||
import { SectionWithDescription } from "../../../components/section-with-description";
|
||||
import { SmtpUpdateEvent, smtpUpdateEventSchema } from "../configuration/smtp-config-input-schema";
|
||||
import { useRouter } from "next/router";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { smtpUrls } from "../urls";
|
||||
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
|
||||
interface EventBoxProps {
|
||||
configuration: SmtpConfiguration;
|
||||
event: SmtpEventConfiguration;
|
||||
}
|
||||
|
||||
const EventBox = ({ event, configuration }: EventBoxProps) => {
|
||||
const router = useRouter();
|
||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
|
||||
const { handleSubmit, control, setError, register } = useForm<SmtpUpdateEvent>({
|
||||
defaultValues: {
|
||||
id: configuration.id,
|
||||
...event,
|
||||
},
|
||||
resolver: zodResolver(smtpUpdateEventSchema),
|
||||
});
|
||||
|
||||
const trpcContext = trpcClient.useContext();
|
||||
const { mutate } = trpcClient.smtpConfiguration.updateEvent.useMutation({
|
||||
onSuccess: async () => {
|
||||
notifySuccess("Configuration saved");
|
||||
trpcContext.smtpConfiguration.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
setBackendErrors<SmtpUpdateEvent>({
|
||||
error,
|
||||
setError,
|
||||
notifyError,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit((data, event) => {
|
||||
mutate({
|
||||
...data,
|
||||
});
|
||||
})}
|
||||
>
|
||||
<BoxWithBorder>
|
||||
<Box padding={defaultPadding} display="flex" flexDirection="column" gap={defaultPadding}>
|
||||
<Text variant="heading">{event.eventType}</Text>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
router.push(smtpUrls.eventConfiguration(configuration.id, event.eventType));
|
||||
}}
|
||||
>
|
||||
Edit template
|
||||
</Button>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" placeholder="Enabled" {...register("active")} />
|
||||
<Text paddingLeft={defaultPadding}>Active</Text>
|
||||
</label>
|
||||
</Box>
|
||||
<BoxFooter>
|
||||
<Button type="submit">Save event</Button>
|
||||
</BoxFooter>
|
||||
</BoxWithBorder>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
interface SmtpEventsSectionProps {
|
||||
configuration: SmtpConfiguration;
|
||||
}
|
||||
|
||||
export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) => {
|
||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||
const router = useRouter();
|
||||
|
||||
// Sort events by displayed label
|
||||
const eventsSorted = configuration.events.sort((a, b) =>
|
||||
messageEventTypesLabels[a.eventType].localeCompare(messageEventTypesLabels[b.eventType])
|
||||
);
|
||||
|
||||
const { register, handleSubmit, setError } = useForm<SmtpUpdateEventArray>({
|
||||
defaultValues: {
|
||||
configurationId: configuration.id,
|
||||
events: eventsSorted,
|
||||
},
|
||||
resolver: zodResolver(smtpUpdateEventArraySchema),
|
||||
});
|
||||
|
||||
const trpcContext = trpcClient.useContext();
|
||||
const { mutate } = trpcClient.smtpConfiguration.updateEventArray.useMutation({
|
||||
onSuccess: async () => {
|
||||
notifySuccess("Configuration saved");
|
||||
trpcContext.smtpConfiguration.invalidate();
|
||||
},
|
||||
onError(error) {
|
||||
setBackendErrors<SmtpUpdateEventArray>({ error, setError, notifyError });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<SectionWithDescription
|
||||
title="Events"
|
||||
|
@ -100,11 +68,53 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
|||
</Box>
|
||||
}
|
||||
>
|
||||
<Box display="flex" flexDirection="column" gap={defaultPadding}>
|
||||
{configuration.events.map((event) => (
|
||||
<EventBox key={event.eventType} configuration={configuration} event={event} />
|
||||
))}
|
||||
</Box>
|
||||
<form
|
||||
onSubmit={handleSubmit((data) => {
|
||||
mutate(data);
|
||||
})}
|
||||
>
|
||||
<BoxWithBorder>
|
||||
<Box padding={defaultPadding}>
|
||||
<Table.Container>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell __width={40}>Active</Table.HeaderCell>
|
||||
<Table.HeaderCell>Event type</Table.HeaderCell>
|
||||
<Table.HeaderCell __width={110}></Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{eventsSorted.map((event, index) => (
|
||||
<Table.Row key={event.eventType}>
|
||||
<Table.Cell>
|
||||
<input type="checkbox" {...register(`events.${index}.active`)} />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Text>{messageEventTypesLabels[event.eventType]}</Text>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
smtpUrls.eventConfiguration(configuration.id, event.eventType)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Edit template
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Container>
|
||||
</Box>
|
||||
<BoxFooter>
|
||||
<Button type="submit">Save provider</Button>
|
||||
</BoxFooter>
|
||||
</BoxWithBorder>
|
||||
</form>
|
||||
</SectionWithDescription>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { appUrls } from "../../../../../modules/app-configuration/urls";
|
|||
import { EventForm } from "../../../../../modules/smtp/ui/event-form";
|
||||
import { smtpUrls } from "../../../../../modules/smtp/urls";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
import { messageEventTypesLabels } from "../../../../../modules/event-handlers/message-event-types";
|
||||
|
||||
const LoadingView = () => {
|
||||
return (
|
||||
|
@ -78,12 +79,12 @@ const EditSmtpEventPage: NextPage = () => {
|
|||
breadcrumbs={[
|
||||
{ name: "Configuration", href: appUrls.configuration() },
|
||||
{ name: `SMTP: ${configuration.name}`, href: smtpUrls.configuration(configurationId) },
|
||||
{ name: eventType },
|
||||
{ name: messageEventTypesLabels[eventType] },
|
||||
]}
|
||||
>
|
||||
<Box display="flex" flexDirection="column" gap={10}>
|
||||
<Text as="p">
|
||||
Edit template for {eventType} event. You can learn more about MJML{" "}
|
||||
Edit template for <code>{eventType}</code> event. You can learn more about MJML{" "}
|
||||
<TextLink href="https://mjml.io/" newTab={true}>
|
||||
here
|
||||
</TextLink>
|
||||
|
|
Loading…
Reference in a new issue