📧 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 = () => (
|
export const ConfigurationNameDescriptionText = () => (
|
||||||
<Text as="p">
|
<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 />
|
<br />
|
||||||
For example - <code>production</code> and <code>development</code>.
|
For example - <code>production</code> and <code>development</code>.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
||||||
ORDER_FULLY_PAID: "Order fully paid",
|
ORDER_FULLY_PAID: "Order fully paid",
|
||||||
INVOICE_SENT: "Invoice sent",
|
INVOICE_SENT: "Invoice sent",
|
||||||
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
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_REQUEST: "Customer account change email request",
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Customer account change email confirmation",
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Customer account change email confirmation",
|
||||||
ACCOUNT_DELETE: "Customer account delete request",
|
ACCOUNT_DELETE: "Customer account delete request",
|
||||||
|
|
|
@ -86,3 +86,10 @@ export const sendgridUpdateEventSchema = sendgridConfigurationEventSchema.merge(
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SendgridUpdateEvent = z.infer<typeof sendgridUpdateEventSchema>;
|
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,
|
sendgridGetEventConfigurationInputSchema,
|
||||||
sendgridUpdateApiConnectionSchema,
|
sendgridUpdateApiConnectionSchema,
|
||||||
sendgridUpdateBasicInformationSchema,
|
sendgridUpdateBasicInformationSchema,
|
||||||
sendgridUpdateEventConfigurationInputSchema,
|
sendgridUpdateEventArraySchema,
|
||||||
sendgridUpdateEventSchema,
|
sendgridUpdateEventSchema,
|
||||||
sendgridUpdateSenderSchema,
|
sendgridUpdateSenderSchema,
|
||||||
} from "./sendgrid-config-input-schema";
|
} from "./sendgrid-config-input-schema";
|
||||||
|
@ -147,23 +147,6 @@ export const sendgridConfigurationRouter = router({
|
||||||
throwTrpcErrorFromConfigurationServiceError(e);
|
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
|
updateBasicInformation: protectedWithConfigurationService
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||||
.input(sendgridUpdateBasicInformationSchema)
|
.input(sendgridUpdateBasicInformationSchema)
|
||||||
|
@ -260,13 +243,41 @@ export const sendgridConfigurationRouter = router({
|
||||||
|
|
||||||
logger.debug(input, "sendgridConfigurationRouter.updateEvent called");
|
logger.debug(input, "sendgridConfigurationRouter.updateEvent called");
|
||||||
|
|
||||||
|
const { id: configurationId, eventType, ...eventConfiguration } = input;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await ctx.sendgridConfigurationService.updateEventConfiguration({
|
return await ctx.sendgridConfigurationService.updateEventConfiguration({
|
||||||
eventConfiguration: input,
|
configurationId,
|
||||||
configurationId: input.id,
|
eventType,
|
||||||
|
eventConfiguration,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throwTrpcErrorFromConfigurationServiceError(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({
|
await service.updateEventConfiguration({
|
||||||
configurationId: validConfig.configurations[0].id,
|
configurationId: validConfig.configurations[0].id,
|
||||||
|
eventType: validConfig.configurations[0].events[0].eventType,
|
||||||
eventConfiguration: {
|
eventConfiguration: {
|
||||||
...validConfig.configurations[0].events[0],
|
...validConfig.configurations[0].events[0],
|
||||||
template: "42",
|
template: "42",
|
||||||
|
@ -544,6 +545,7 @@ describe("SendgridConfigurationService", function () {
|
||||||
await expect(async () =>
|
await expect(async () =>
|
||||||
service.updateEventConfiguration({
|
service.updateEventConfiguration({
|
||||||
configurationId: "this-id-does-not-exist",
|
configurationId: "this-id-does-not-exist",
|
||||||
|
eventType: validConfig.configurations[0].events[0].eventType,
|
||||||
eventConfiguration: {
|
eventConfiguration: {
|
||||||
...validConfig.configurations[0].events[0],
|
...validConfig.configurations[0].events[0],
|
||||||
template: "42",
|
template: "42",
|
||||||
|
|
|
@ -234,19 +234,19 @@ export class SendgridConfigurationService {
|
||||||
*/
|
*/
|
||||||
async updateEventConfiguration({
|
async updateEventConfiguration({
|
||||||
configurationId,
|
configurationId,
|
||||||
|
eventType,
|
||||||
eventConfiguration,
|
eventConfiguration,
|
||||||
}: {
|
}: {
|
||||||
configurationId: string;
|
configurationId: string;
|
||||||
eventConfiguration: SendgridEventConfiguration;
|
eventType: SendgridEventConfiguration["eventType"];
|
||||||
|
eventConfiguration: Partial<Omit<SendgridEventConfiguration, "eventType">>;
|
||||||
}) {
|
}) {
|
||||||
logger.debug("Update event configuration");
|
logger.debug("Update event configuration");
|
||||||
const configuration = await this.getConfiguration({
|
const configuration = await this.getConfiguration({
|
||||||
id: configurationId,
|
id: configurationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventIndex = configuration.events.findIndex(
|
const eventIndex = configuration.events.findIndex((e) => e.eventType === eventType);
|
||||||
(e) => e.eventType === eventConfiguration.eventType
|
|
||||||
);
|
|
||||||
|
|
||||||
if (eventIndex < 0) {
|
if (eventIndex < 0) {
|
||||||
logger.warn("Event configuration not found, throwing an error");
|
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);
|
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 { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import {
|
import {
|
||||||
SendgridUpdateEvent,
|
SendgridUpdateEventArray,
|
||||||
sendgridUpdateEventSchema,
|
sendgridUpdateEventArraySchema,
|
||||||
} from "../configuration/sendgrid-config-input-schema";
|
} from "../configuration/sendgrid-config-input-schema";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { BoxFooter } from "../../../components/box-footer";
|
import { BoxFooter } from "../../../components/box-footer";
|
||||||
|
@ -18,91 +18,57 @@ import { useQuery } from "@tanstack/react-query";
|
||||||
import { fetchTemplates } from "../sendgrid-api";
|
import { fetchTemplates } from "../sendgrid-api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
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";
|
import { TextLink } from "@saleor/apps-ui";
|
||||||
|
import { messageEventTypesLabels } from "../../event-handlers/message-event-types";
|
||||||
interface EventBoxProps {
|
import { Table } from "../../../components/table";
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SendgridEventsSectionProps {
|
interface SendgridEventsSectionProps {
|
||||||
configuration: SendgridConfiguration;
|
configuration: SendgridConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionProps) => {
|
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 (
|
return (
|
||||||
<SectionWithDescription
|
<SectionWithDescription
|
||||||
title="Events"
|
title="Events"
|
||||||
description={
|
description={
|
||||||
<Text as="p">
|
<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}>
|
<TextLink href="https://mc.sendgrid.com/dynamic-templates" newTab={true}>
|
||||||
Sendgrid dashboard
|
Sendgrid dashboard
|
||||||
</TextLink>
|
</TextLink>
|
||||||
|
@ -110,11 +76,47 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box display="flex" flexDirection="column" gap={defaultPadding}>
|
<form
|
||||||
{configuration.events.map((event) => (
|
onSubmit={handleSubmit((data) => {
|
||||||
<EventBox key={event.eventType} configuration={configuration} event={event} />
|
mutate(data);
|
||||||
))}
|
})}
|
||||||
</Box>
|
>
|
||||||
|
<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>
|
</SectionWithDescription>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -72,6 +72,21 @@ export const smtpUpdateEventSchema = smtpConfigurationEventSchema.merge(
|
||||||
|
|
||||||
export type SmtpUpdateEvent = z.infer<typeof smtpUpdateEventSchema>;
|
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(
|
export const smtpGetEventConfigurationInputSchema = smtpConfigurationIdInputSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
eventType: z.enum(messageEventTypes),
|
eventType: z.enum(messageEventTypes),
|
||||||
|
@ -87,3 +102,10 @@ export const smtpUpdateEventConfigurationInputSchema = smtpConfigurationIdInputS
|
||||||
export type SmtpUpdateEventConfigurationInput = z.infer<
|
export type SmtpUpdateEventConfigurationInput = z.infer<
|
||||||
typeof smtpUpdateEventConfigurationInputSchema
|
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,
|
smtpGetConfigurationsInputSchema,
|
||||||
smtpGetEventConfigurationInputSchema,
|
smtpGetEventConfigurationInputSchema,
|
||||||
smtpUpdateBasicInformationSchema,
|
smtpUpdateBasicInformationSchema,
|
||||||
smtpUpdateEventConfigurationInputSchema,
|
smtpUpdateEventActiveStatusInputSchema,
|
||||||
|
smtpUpdateEventArraySchema,
|
||||||
smtpUpdateEventSchema,
|
smtpUpdateEventSchema,
|
||||||
smtpUpdateSenderSchema,
|
smtpUpdateSenderSchema,
|
||||||
smtpUpdateSmtpSchema,
|
smtpUpdateSmtpSchema,
|
||||||
|
@ -150,23 +151,6 @@ export const smtpConfigurationRouter = router({
|
||||||
throwTrpcErrorFromConfigurationServiceError(e);
|
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
|
renderTemplate: protectedWithConfigurationService
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||||
|
@ -287,13 +271,59 @@ export const smtpConfigurationRouter = router({
|
||||||
|
|
||||||
logger.debug(input, "smtpConfigurationRouter.updateEvent called");
|
logger.debug(input, "smtpConfigurationRouter.updateEvent called");
|
||||||
|
|
||||||
|
const { id: configurationId, eventType, ...eventConfiguration } = input;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
||||||
eventConfiguration: input,
|
configurationId,
|
||||||
configurationId: input.id,
|
eventType,
|
||||||
|
eventConfiguration,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throwTrpcErrorFromConfigurationServiceError(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({
|
await service.updateEventConfiguration({
|
||||||
configurationId: validConfig.configurations[0].id,
|
configurationId: validConfig.configurations[0].id,
|
||||||
|
eventType: validConfig.configurations[0].events[0].eventType,
|
||||||
eventConfiguration: {
|
eventConfiguration: {
|
||||||
...validConfig.configurations[0].events[0],
|
...validConfig.configurations[0].events[0],
|
||||||
subject: "Updated subject",
|
subject: "Updated subject",
|
||||||
|
@ -571,6 +572,7 @@ describe("SmtpConfigurationService", function () {
|
||||||
await expect(async () =>
|
await expect(async () =>
|
||||||
service.updateEventConfiguration({
|
service.updateEventConfiguration({
|
||||||
configurationId: "this-id-does-not-exist",
|
configurationId: "this-id-does-not-exist",
|
||||||
|
eventType: validConfig.configurations[0].events[0].eventType,
|
||||||
eventConfiguration: {
|
eventConfiguration: {
|
||||||
...validConfig.configurations[0].events[0],
|
...validConfig.configurations[0].events[0],
|
||||||
subject: "Updated subject",
|
subject: "Updated subject",
|
||||||
|
|
|
@ -225,18 +225,18 @@ export class SmtpConfigurationService {
|
||||||
async updateEventConfiguration({
|
async updateEventConfiguration({
|
||||||
configurationId,
|
configurationId,
|
||||||
eventConfiguration,
|
eventConfiguration,
|
||||||
|
eventType,
|
||||||
}: {
|
}: {
|
||||||
configurationId: string;
|
configurationId: string;
|
||||||
eventConfiguration: SmtpEventConfiguration;
|
eventType: SmtpEventConfiguration["eventType"];
|
||||||
|
eventConfiguration: Partial<Omit<SmtpEventConfiguration, "eventType">>;
|
||||||
}) {
|
}) {
|
||||||
logger.debug("Update event configuration");
|
logger.debug("Update event configuration");
|
||||||
const configuration = await this.getConfiguration({
|
const configuration = await this.getConfiguration({
|
||||||
id: configurationId,
|
id: configurationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventIndex = configuration.events.findIndex(
|
const eventIndex = configuration.events.findIndex((e) => e.eventType === eventType);
|
||||||
(e) => e.eventType === eventConfiguration.eventType
|
|
||||||
);
|
|
||||||
|
|
||||||
if (eventIndex < 0) {
|
if (eventIndex < 0) {
|
||||||
logger.warn("Event configuration not found, throwing an error");
|
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);
|
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 { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import {
|
|
||||||
SmtpUpdateEventConfigurationInput,
|
|
||||||
smtpUpdateEventConfigurationInputSchema,
|
|
||||||
} from "../configuration/smtp-config-input-schema";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { CodeEditor } from "./code-edtor";
|
import { CodeEditor } from "./code-editor";
|
||||||
import { useDebounce } from "usehooks-ts";
|
import { useDebounce } from "usehooks-ts";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { examplePayloads } from "../../event-handlers/default-payloads";
|
import { examplePayloads } from "../../event-handlers/default-payloads";
|
||||||
|
@ -17,6 +13,7 @@ import { MjmlPreview } from "./mjml-preview";
|
||||||
import { defaultPadding } from "../../../components/ui-defaults";
|
import { defaultPadding } from "../../../components/ui-defaults";
|
||||||
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
import { setBackendErrors } from "../../../lib/set-backend-errors";
|
||||||
import { Input } from "@saleor/react-hook-form-macaw";
|
import { Input } from "@saleor/react-hook-form-macaw";
|
||||||
|
import { SmtpUpdateEvent, smtpUpdateEventSchema } from "../configuration/smtp-config-input-schema";
|
||||||
const PREVIEW_DEBOUNCE_DELAY = 500;
|
const PREVIEW_DEBOUNCE_DELAY = 500;
|
||||||
|
|
||||||
interface EventFormProps {
|
interface EventFormProps {
|
||||||
|
@ -31,15 +28,13 @@ export const EventForm = ({ configuration, eventType }: EventFormProps) => {
|
||||||
(eventConfiguration) => eventConfiguration.eventType === eventType
|
(eventConfiguration) => eventConfiguration.eventType === eventType
|
||||||
)!; // Event conf is not optional, so we can use ! here
|
)!; // Event conf is not optional, so we can use ! here
|
||||||
|
|
||||||
const { handleSubmit, control, getValues, setError } = useForm<SmtpUpdateEventConfigurationInput>(
|
const { handleSubmit, control, getValues, setError } = useForm<SmtpUpdateEvent>({
|
||||||
{
|
defaultValues: {
|
||||||
defaultValues: {
|
id: configuration.id,
|
||||||
id: configuration.id,
|
...eventConfiguration,
|
||||||
...eventConfiguration,
|
},
|
||||||
},
|
resolver: zodResolver(smtpUpdateEventSchema),
|
||||||
resolver: zodResolver(smtpUpdateEventConfigurationInputSchema),
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const trpcContext = trpcClient.useContext();
|
const trpcContext = trpcClient.useContext();
|
||||||
const { mutate } = trpcClient.smtpConfiguration.updateEvent.useMutation({
|
const { mutate } = trpcClient.smtpConfiguration.updateEvent.useMutation({
|
||||||
|
@ -48,7 +43,7 @@ export const EventForm = ({ configuration, eventType }: EventFormProps) => {
|
||||||
trpcContext.smtpConfiguration.invalidate();
|
trpcContext.smtpConfiguration.invalidate();
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
setBackendErrors<SmtpUpdateEventConfigurationInput>({
|
setBackendErrors<SmtpUpdateEvent>({
|
||||||
error,
|
error,
|
||||||
setError,
|
setError,
|
||||||
notifyError,
|
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 { BoxWithBorder } from "../../../components/box-with-border";
|
||||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import { defaultPadding } from "../../../components/ui-defaults";
|
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 { 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 { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { useForm } from "react-hook-form";
|
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 { 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 {
|
interface SmtpEventsSectionProps {
|
||||||
configuration: SmtpConfiguration;
|
configuration: SmtpConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) => {
|
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 (
|
return (
|
||||||
<SectionWithDescription
|
<SectionWithDescription
|
||||||
title="Events"
|
title="Events"
|
||||||
|
@ -100,11 +68,53 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box display="flex" flexDirection="column" gap={defaultPadding}>
|
<form
|
||||||
{configuration.events.map((event) => (
|
onSubmit={handleSubmit((data) => {
|
||||||
<EventBox key={event.eventType} configuration={configuration} event={event} />
|
mutate(data);
|
||||||
))}
|
})}
|
||||||
</Box>
|
>
|
||||||
|
<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>
|
</SectionWithDescription>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { appUrls } from "../../../../../modules/app-configuration/urls";
|
||||||
import { EventForm } from "../../../../../modules/smtp/ui/event-form";
|
import { EventForm } from "../../../../../modules/smtp/ui/event-form";
|
||||||
import { smtpUrls } from "../../../../../modules/smtp/urls";
|
import { smtpUrls } from "../../../../../modules/smtp/urls";
|
||||||
import { TextLink } from "@saleor/apps-ui";
|
import { TextLink } from "@saleor/apps-ui";
|
||||||
|
import { messageEventTypesLabels } from "../../../../../modules/event-handlers/message-event-types";
|
||||||
|
|
||||||
const LoadingView = () => {
|
const LoadingView = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -78,12 +79,12 @@ const EditSmtpEventPage: NextPage = () => {
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: "Configuration", href: appUrls.configuration() },
|
{ name: "Configuration", href: appUrls.configuration() },
|
||||||
{ name: `SMTP: ${configuration.name}`, href: smtpUrls.configuration(configurationId) },
|
{ name: `SMTP: ${configuration.name}`, href: smtpUrls.configuration(configurationId) },
|
||||||
{ name: eventType },
|
{ name: messageEventTypesLabels[eventType] },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Box display="flex" flexDirection="column" gap={10}>
|
<Box display="flex" flexDirection="column" gap={10}>
|
||||||
<Text as="p">
|
<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}>
|
<TextLink href="https://mjml.io/" newTab={true}>
|
||||||
here
|
here
|
||||||
</TextLink>
|
</TextLink>
|
||||||
|
|
Loading…
Reference in a new issue