diff --git a/apps/emails-and-messages/package.json b/apps/emails-and-messages/package.json index a672ecc..aacf571 100644 --- a/apps/emails-and-messages/package.json +++ b/apps/emails-and-messages/package.json @@ -19,7 +19,7 @@ "@monaco-editor/react": "^4.4.6", "@saleor/app-sdk": "0.37.3", "@saleor/apps-shared": "workspace:*", - "@saleor/macaw-ui": "0.8.0-pre.72", + "@saleor/macaw-ui": "0.8.0-pre.76", "@sendgrid/client": "^7.7.0", "@sendgrid/mail": "^7.7.0", "@tanstack/react-query": "^4.24.4", diff --git a/apps/emails-and-messages/src/components/box-footer.tsx b/apps/emails-and-messages/src/components/box-footer.tsx new file mode 100644 index 0000000..f588b37 --- /dev/null +++ b/apps/emails-and-messages/src/components/box-footer.tsx @@ -0,0 +1,19 @@ +import { Box, BoxProps } from "@saleor/macaw-ui/next"; +import { defaultPadding } from "./ui-defaults"; + +export const BoxFooter = (props: BoxProps) => { + return ( + + {props.children} + + ); +}; diff --git a/apps/emails-and-messages/src/components/box-with-border.tsx b/apps/emails-and-messages/src/components/box-with-border.tsx new file mode 100644 index 0000000..c2ea45a --- /dev/null +++ b/apps/emails-and-messages/src/components/box-with-border.tsx @@ -0,0 +1,15 @@ +import { Box, BoxProps } from "@saleor/macaw-ui/next"; + +export const BoxWithBorder = (props: BoxProps) => { + return ( + + {props.children} + + ); +}; diff --git a/apps/emails-and-messages/src/components/breadcrumbs.tsx b/apps/emails-and-messages/src/components/breadcrumbs.tsx new file mode 100644 index 0000000..661e761 --- /dev/null +++ b/apps/emails-and-messages/src/components/breadcrumbs.tsx @@ -0,0 +1,33 @@ +import { Box, Text } from "@saleor/macaw-ui/next"; + +interface BreadcrumbsProps { + items: Array<{ name: string; href?: string }>; +} + +export const Breadcrumbs = (props: BreadcrumbsProps) => { + if (props.items.length === 0) { + return null; + } + + // TODO: do I have to recreate the array here? + const i = [...props.items]; + const lastItem = i.pop()!; // can enforce the type since array is at least one element long + + return ( + + {i.map((item) => ( + <> + + {item.name} + + + {">"} + + + ))} + + {lastItem.name} + + + ); +}; diff --git a/apps/emails-and-messages/src/components/loading-indicator.tsx b/apps/emails-and-messages/src/components/loading-indicator.tsx new file mode 100644 index 0000000..9d999e4 --- /dev/null +++ b/apps/emails-and-messages/src/components/loading-indicator.tsx @@ -0,0 +1,3 @@ +export const LoadingIndicator = () => { + return <>LOADING; +}; diff --git a/apps/emails-and-messages/src/components/messaging-providers-box.tsx b/apps/emails-and-messages/src/components/messaging-providers-box.tsx new file mode 100644 index 0000000..5a3da8a --- /dev/null +++ b/apps/emails-and-messages/src/components/messaging-providers-box.tsx @@ -0,0 +1,75 @@ +import { Box, Button, Text } from "@saleor/macaw-ui/next"; +import { BoxWithBorder } from "./box-with-border"; +import { BoxFooter } from "./box-footer"; +import { defaultPadding } from "./ui-defaults"; +import { useRouter } from "next/router"; +import { SendgridConfiguration } from "../modules/sendgrid/configuration/sendgrid-config"; + +const NoExistingConfigurations = () => { + const { replace } = useRouter(); + + const redirectToProvidersSelection = () => { + replace("/configuration/choose-provider"); + }; + + return ( + + No providers configured yet + + + ); +}; + +interface MessagingProvidersSectionProps { + configurations: SendgridConfiguration[]; + isLoading: boolean; +} + +export const MessagingProvidersBox = ({ + configurations, + isLoading: loading, +}: MessagingProvidersSectionProps) => { + const { replace } = useRouter(); + + if (loading) { + return ( + + Loading + + ); + } + + if (configurations.length === 0) { + return ; + } + + const redirectToProvidersSelection = () => { + replace("/configuration/choose-provider"); + }; + + const redirectToEditConfiguration = (configurationId: string) => { + replace(`/configuration/sendgrid/edit/${configurationId}`); + }; + + return ( + + + Provider + Name + Status + + {configurations.map((configuration) => ( + <> + Sendgrid + {configuration.configurationName} + {configuration.active} + redirectToEditConfiguration(configuration.id)}>Edit + + ))} + + + + + + ); +}; diff --git a/apps/emails-and-messages/src/components/section-with-description.tsx b/apps/emails-and-messages/src/components/section-with-description.tsx new file mode 100644 index 0000000..463f629 --- /dev/null +++ b/apps/emails-and-messages/src/components/section-with-description.tsx @@ -0,0 +1,27 @@ +import { Box, Text } from "@saleor/macaw-ui/next"; + +interface SectionWithDescriptionProps { + title: string; + description?: React.ReactNode; + children?: React.ReactNode; +} +export const SectionWithDescription = (props: SectionWithDescriptionProps) => { + return ( + + + + {props.title} + + {props.description} + + {!!props.children && ( + + {props.children} + + )} + + ); +}; diff --git a/apps/emails-and-messages/src/components/temp.tsx b/apps/emails-and-messages/src/components/temp.tsx new file mode 100644 index 0000000..5d6454c --- /dev/null +++ b/apps/emails-and-messages/src/components/temp.tsx @@ -0,0 +1,160 @@ +const x = () => ( + + + Provide unique name for your configuration - you can create more than one. For example + - production and development. + + Then, pass your API Key. Obtain it here. + + } + > + + + + Remove provider + + You can remove provider configuration. + + This operation will remove all settings related to this configuration. Data will be + permanently removed from the App.{" "} + + + This operation cant be undone. You still can create new configuration. + + + + + + + + + + Provide unique name for your configuration - you can create more than one. For example + - production and development. + + Then, pass your API Key. Obtain it here. + + } + > + + + + + + + Enabled + + + + + + + Enabled + + + + + + + Enabled + + + + + + + + + + + Provide unique name for your configuration - you can create more than one. For example + - production and development. + + Then, pass your API Key. Obtain it here. + + } + > + + + + + + + + + + + + Manage providers configuration to connect Saleor events with 3rd party services. + + } + > + + No providers configured yet + + + + + + console.log("clicked sendgrid")} + /> + + console.log("clicked mjml")} + /> + + +) diff --git a/apps/emails-and-messages/src/components/ui-defaults.ts b/apps/emails-and-messages/src/components/ui-defaults.ts new file mode 100644 index 0000000..cb54a9c --- /dev/null +++ b/apps/emails-and-messages/src/components/ui-defaults.ts @@ -0,0 +1 @@ +export const defaultPadding = 6; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-input-schema.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-input-schema.ts index b6dd299..615a2af 100644 --- a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-input-schema.ts +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-input-schema.ts @@ -20,10 +20,15 @@ export const sendgridConfigurationBaseObjectSchema = z.object({ }), }); -export const sendgridCreateConfigurationSchema = sendgridConfigurationBaseObjectSchema.omit({ - senderEmail: true, - senderName: true, +export const sendgridCreateConfigurationSchema = sendgridConfigurationBaseObjectSchema.pick({ + configurationName: true, + apiKey: true, }); + +export type SendgridCreateConfigurationSchemaType = z.infer< + typeof sendgridCreateConfigurationSchema +>; + export const sendgridUpdateOrCreateConfigurationSchema = sendgridConfigurationBaseObjectSchema.merge( z.object({ @@ -53,3 +58,10 @@ export const sendgridGetEventConfigurationInputSchema = z.object({ configurationId: z.string(), eventType: z.enum(messageEventTypes), }); + +export const sendgridUpdateBasicInformationSchema = sendgridConfigurationBaseObjectSchema + .pick({ + configurationName: true, + active: true, + }) + .merge(z.object({ id: z.string() })); diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.router.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.router.ts index 99253e7..cd02720 100644 --- a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.router.ts +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.router.ts @@ -5,6 +5,7 @@ import { sendgridGetConfigurationInputSchema, sendgridGetConfigurationsInputSchema, sendgridGetEventConfigurationInputSchema, + sendgridUpdateBasicInformationSchema, sendgridUpdateEventConfigurationInputSchema, sendgridUpdateOrCreateConfigurationSchema, } from "./sendgrid-config-input-schema"; @@ -156,4 +157,22 @@ export const sendgridConfigurationRouter = router({ await ctx.configurationService.updateConfiguration(configuration); return configuration; }), + updateBasicInformation: protectedWithConfigurationService + .meta({ requiredClientPermissions: ["MANAGE_APPS"] }) + .input(sendgridUpdateBasicInformationSchema) + .mutation(async ({ ctx, input }) => { + const configuration = await ctx.configurationService.getConfiguration({ + id: input.id, + }); + + if (!configuration) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Configuration not found", + }); + } + + await ctx.configurationService.updateConfiguration({ ...configuration, ...input }); + return configuration; + }), }); diff --git a/apps/emails-and-messages/src/modules/sendgrid/ui/api-connection-section.tsx b/apps/emails-and-messages/src/modules/sendgrid/ui/api-connection-section.tsx new file mode 100644 index 0000000..e9f4513 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/ui/api-connection-section.tsx @@ -0,0 +1,126 @@ +import { useRouter } from "next/router"; +import { SendgridConfiguration } from "../configuration/sendgrid-config"; +import { BoxWithBorder } from "../../../components/box-with-border"; +import { Box, Button, Input, RadioGroup, Text } from "@saleor/macaw-ui/next"; +import { defaultPadding } from "../../../components/ui-defaults"; +import { useDashboardNotification } from "@saleor/apps-shared"; +import { trpcClient } from "../../trpc/trpc-client"; +import { sendgridUpdateBasicInformationSchema } from "../configuration/sendgrid-config-input-schema"; +import { z } from "zod"; +import { Controller, useForm } from "react-hook-form"; +import { BoxFooter } from "../../../components/box-footer"; +import { SectionWithDescription } from "../../../components/section-with-description"; + +interface ApiConnectionSectionProps { + configuration?: SendgridConfiguration; +} + +export const ApiConnectionSection = ({ configuration }: ApiConnectionSectionProps) => { + const { replace } = useRouter(); + const { notifySuccess, notifyError } = useDashboardNotification(); + + const { handleSubmit, control, setError } = useForm< + z.infer + >({ + defaultValues: { + configurationName: configuration?.configurationName, + active: configuration?.active, + }, + }); + + const { mutate: createConfiguration } = + trpcClient.sendgridConfiguration.updateBasicInformation.useMutation({ + onSuccess: async (data, variables) => { + notifySuccess("Configuration saved"); + // TODO: redirect to configuration details based on id + }, + onError(error) { + let isFieldErrorSet = false; + const fieldErrors = error.data?.zodError?.fieldErrors || {}; + for (const fieldName in fieldErrors) { + for (const message of fieldErrors[fieldName] || []) { + isFieldErrorSet = true; + setError(fieldName as keyof z.infer, { + type: "manual", + message, + }); + } + } + const formErrors = error.data?.zodError?.formErrors || []; + const formErrorMessage = formErrors.length ? formErrors.join("\n") : undefined; + + notifyError( + "Could not save the configuration", + isFieldErrorSet ? "Submitted form contain errors" : "Error saving configuration", + formErrorMessage + ); + }, + }); + + if (!configuration) { + return ( + + Loading + + ); + } + + return ( + + +
{ + createConfiguration({ + ...data, + }); + })} + > + + ( + + )} + /> + ( + // TODO: add validation + console.log(e)}> + + Live + + + Sandbox + + + )} + /> + + + + +
+
+
+ ); +}; diff --git a/apps/emails-and-messages/src/modules/sendgrid/ui/basic-information-section.tsx b/apps/emails-and-messages/src/modules/sendgrid/ui/basic-information-section.tsx new file mode 100644 index 0000000..81283da --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/ui/basic-information-section.tsx @@ -0,0 +1,134 @@ +import { useRouter } from "next/router"; +import { SendgridConfiguration } from "../configuration/sendgrid-config"; +import { BoxWithBorder } from "../../../components/box-with-border"; +import { Box, Button, Input, RadioGroup, Text } from "@saleor/macaw-ui/next"; +import { defaultPadding } from "../../../components/ui-defaults"; +import { useDashboardNotification } from "@saleor/apps-shared"; +import { trpcClient } from "../../trpc/trpc-client"; +import { sendgridUpdateBasicInformationSchema } from "../configuration/sendgrid-config-input-schema"; +import { z } from "zod"; +import { Controller, useForm } from "react-hook-form"; +import { BoxFooter } from "../../../components/box-footer"; +import { SectionWithDescription } from "../../../components/section-with-description"; + +interface BasicInformationSectionProps { + configuration?: SendgridConfiguration; +} + +export const BasicInformationSection = ({ configuration }: BasicInformationSectionProps) => { + const { replace } = useRouter(); + const { notifySuccess, notifyError } = useDashboardNotification(); + + const { handleSubmit, control, setError } = useForm< + z.infer + >({ + defaultValues: { + configurationName: configuration?.configurationName, + active: configuration?.active, + }, + }); + + const { mutate: createConfiguration } = + trpcClient.sendgridConfiguration.updateBasicInformation.useMutation({ + onSuccess: async (data, variables) => { + notifySuccess("Configuration saved"); + // TODO: redirect to configuration details based on id + }, + onError(error) { + let isFieldErrorSet = false; + const fieldErrors = error.data?.zodError?.fieldErrors || {}; + for (const fieldName in fieldErrors) { + for (const message of fieldErrors[fieldName] || []) { + isFieldErrorSet = true; + setError(fieldName as keyof z.infer, { + type: "manual", + message, + }); + } + } + const formErrors = error.data?.zodError?.formErrors || []; + const formErrorMessage = formErrors.length ? formErrors.join("\n") : undefined; + + notifyError( + "Could not save the configuration", + isFieldErrorSet ? "Submitted form contain errors" : "Error saving configuration", + formErrorMessage + ); + }, + }); + + if (!configuration) { + return ( + + Loading + + ); + } + + return ( + + Provide unique name for your configuration - you can create more than one. For example - + production and development. Then, pass your API Key. Obtain it here. + + } + > + +
{ + createConfiguration({ + ...data, + }); + })} + > + + ( + + )} + /> + ( + // TODO: add validation + console.log(e)}> + + Active + + + Disabled + + + )} + /> + + + + +
+
+
+ ); +}; diff --git a/apps/emails-and-messages/src/modules/sendgrid/ui/dangrous-section.tsx b/apps/emails-and-messages/src/modules/sendgrid/ui/dangrous-section.tsx new file mode 100644 index 0000000..393c178 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/ui/dangrous-section.tsx @@ -0,0 +1,44 @@ +import { Box, Button, Text } from "@saleor/macaw-ui/next"; +import { BoxWithBorder } from "../../../components/box-with-border"; +import { SectionWithDescription } from "../../../components/section-with-description"; +import { defaultPadding } from "../../../components/ui-defaults"; +import { BoxFooter } from "../../../components/box-footer"; +import { SendgridConfiguration } from "../configuration/sendgrid-config"; + +interface DangerousSectionProps { + configuration?: SendgridConfiguration; +} + +export const DangerousSection = ({ configuration }: DangerousSectionProps) => { + const onRemoveConfiguration = () => { + console.log("remove", configuration?.id); + }; + + return ( + + + + + Remove provider + + You can remove provider configuration. + + This operation will remove all settings related to this configuration. Data will be + permanently removed from the App.{" "} + + This operation cant be undone. + You still can create new configuration. + + + + + + + ); +}; diff --git a/apps/emails-and-messages/src/modules/sendgrid/ui/sender-section.tsx b/apps/emails-and-messages/src/modules/sendgrid/ui/sender-section.tsx new file mode 100644 index 0000000..de89bc6 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/ui/sender-section.tsx @@ -0,0 +1,105 @@ +import { useRouter } from "next/router"; +import { SendgridConfiguration } from "../configuration/sendgrid-config"; +import { BoxWithBorder } from "../../../components/box-with-border"; +import { Box, Button, Combobox, Text } from "@saleor/macaw-ui/next"; +import { defaultPadding } from "../../../components/ui-defaults"; +import { useDashboardNotification } from "@saleor/apps-shared"; +import { trpcClient } from "../../trpc/trpc-client"; +import { sendgridUpdateBasicInformationSchema } from "../configuration/sendgrid-config-input-schema"; +import { z } from "zod"; +import { Controller, useForm } from "react-hook-form"; +import { BoxFooter } from "../../../components/box-footer"; +import { SectionWithDescription } from "../../../components/section-with-description"; + +interface SenderSectionProps { + configuration?: SendgridConfiguration; +} + +export const SenderSection = ({ configuration }: SenderSectionProps) => { + const { replace } = useRouter(); + const { notifySuccess, notifyError } = useDashboardNotification(); + + const { handleSubmit, control, setError } = useForm< + z.infer + >({ + defaultValues: { + configurationName: configuration?.configurationName, + active: configuration?.active, + }, + }); + + const { mutate: createConfiguration } = + trpcClient.sendgridConfiguration.updateBasicInformation.useMutation({ + onSuccess: async (data, variables) => { + notifySuccess("Configuration saved"); + // TODO: redirect to configuration details based on id + }, + onError(error) { + let isFieldErrorSet = false; + const fieldErrors = error.data?.zodError?.fieldErrors || {}; + for (const fieldName in fieldErrors) { + for (const message of fieldErrors[fieldName] || []) { + isFieldErrorSet = true; + setError(fieldName as keyof z.infer, { + type: "manual", + message, + }); + } + } + const formErrors = error.data?.zodError?.formErrors || []; + const formErrorMessage = formErrors.length ? formErrors.join("\n") : undefined; + + notifyError( + "Could not save the configuration", + isFieldErrorSet ? "Submitted form contain errors" : "Error saving configuration", + formErrorMessage + ); + }, + }); + + if (!configuration) { + return ( + + Loading + + ); + } + + return ( + + +
{ + createConfiguration({ + ...data, + }); + })} + > + + ( + + )} + /> + + + + +
+
+
+ ); +}; diff --git a/apps/emails-and-messages/src/pages/_app.tsx b/apps/emails-and-messages/src/pages/_app.tsx index 125083e..ea838bd 100644 --- a/apps/emails-and-messages/src/pages/_app.tsx +++ b/apps/emails-and-messages/src/pages/_app.tsx @@ -1,65 +1,15 @@ +import "@saleor/macaw-ui/next/style"; import "../styles/globals.css"; -import { Theme } from "@material-ui/core/styles"; import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge"; import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next"; -import { - dark, - light, - SaleorThemeColors, - ThemeProvider as MacawUIThemeProvider, -} from "@saleor/macaw-ui"; -import React, { PropsWithChildren, useEffect } from "react"; +import React from "react"; import { AppProps } from "next/app"; +import { ThemeProvider } from "@saleor/macaw-ui/next"; import { ThemeSynchronizer } from "../lib/theme-synchronizer"; import { NoSSRWrapper } from "../lib/no-ssr-wrapper"; import { trpcClient } from "../modules/trpc/trpc-client"; -import { createGenerateClassName, StylesProvider } from "@material-ui/core"; - -type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>; - -/** - * Temporary override of colors, to match new dashboard palette. - * Long term this will be replaced with Macaw UI 2.x with up to date design tokens - */ -const palettes: PalettesOverride = { - light: { - ...light, - background: { - default: "#fff", - paper: "#fff", - }, - }, - dark: { - ...dark, - background: { - default: "hsla(211, 42%, 14%, 1)", - paper: "hsla(211, 42%, 14%, 1)", - }, - }, -}; - -const themeOverrides: Partial = { - overrides: { - MuiTableCell: { - body: { - paddingBottom: 8, - paddingTop: 8, - }, - root: { - height: 56, - paddingBottom: 4, - paddingTop: 4, - }, - }, - }, -}; - -const generateClassName = createGenerateClassName({ - productionPrefix: "c", - disableGlobal: true, -}); /** * Ensure instance is a singleton. @@ -67,34 +17,15 @@ const generateClassName = createGenerateClassName({ */ export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined; -/** - * That's a hack required by Macaw-UI incompatibility with React@18 - */ -const ThemeProvider = MacawUIThemeProvider as React.FC< - PropsWithChildren<{ overrides?: Partial; ssr: boolean; palettes: PalettesOverride }> ->; - function NextApp({ Component, pageProps }: AppProps) { - /** - * Configure JSS (used by MacawUI) for SSR. If Macaw is not used, can be removed. - */ - useEffect(() => { - const jssStyles = document.querySelector("#jss-server-side"); - if (jssStyles) { - jssStyles?.parentElement?.removeChild(jssStyles); - } - }, []); - return ( - - - - - - - + + + + + ); diff --git a/apps/emails-and-messages/src/pages/api/register.ts b/apps/emails-and-messages/src/pages/api/register.ts index 03e1e55..45d2996 100644 --- a/apps/emails-and-messages/src/pages/api/register.ts +++ b/apps/emails-and-messages/src/pages/api/register.ts @@ -27,16 +27,16 @@ export default createAppRegisterHandler({ ], onAuthAplSaved: async (request, ctx) => { // Subscribe to Notify using the mutation since it does not use subscriptions and can't be subscribed via manifest - logger.debug("onAuthAplSaved executing"); - - const baseUrl = getBaseUrl(request.headers); - const client = createClient(ctx.authData.saleorApiUrl, async () => - Promise.resolve({ token: ctx.authData.token }) - ); - await registerNotifyWebhook({ - client: client, - baseUrl: baseUrl, - }); - logger.debug("Webhook registered"); + // FIXME: + // logger.debug("onAuthAplSaved executing"); + // const baseUrl = getBaseUrl(request.headers); + // const client = createClient(ctx.authData.saleorApiUrl, async () => + // Promise.resolve({ token: ctx.authData.token }) + // ); + // await registerNotifyWebhook({ + // client: client, + // baseUrl: baseUrl, + // }); + // logger.debug("Webhook registered"); }, }); diff --git a/apps/emails-and-messages/src/pages/configuration/choose-provider.tsx b/apps/emails-and-messages/src/pages/configuration/choose-provider.tsx new file mode 100644 index 0000000..672c8b6 --- /dev/null +++ b/apps/emails-and-messages/src/pages/configuration/choose-provider.tsx @@ -0,0 +1,38 @@ +import { Box, Text } from "@saleor/macaw-ui/next"; +import { NextPage } from "next"; +import { Breadcrumbs } from "../../components/breadcrumbs"; +import { SectionWithDescription } from "../../components/section-with-description"; +import { ProviderSelectionBox } from "../../modules/app-configuration/ui/provider-selection-box"; +import { useRouter } from "next/router"; + +const ChooseProviderPage: NextPage = () => { + const { replace } = useRouter(); + + return ( + + + + + Select and configure providers to connect Saleor with selected services. + + + + + replace("/configuration/sendgrid/new")} + /> + + replace("/configuration/mjml/new")} + /> + + + + ); +}; + +export default ChooseProviderPage; diff --git a/apps/emails-and-messages/src/pages/configuration/index.tsx b/apps/emails-and-messages/src/pages/configuration/index.tsx new file mode 100644 index 0000000..3c23ec4 --- /dev/null +++ b/apps/emails-and-messages/src/pages/configuration/index.tsx @@ -0,0 +1,36 @@ +import { Box, Text } from "@saleor/macaw-ui/next"; +import { NextPage } from "next"; +import { Breadcrumbs } from "../../components/breadcrumbs"; +import { SectionWithDescription } from "../../components/section-with-description"; +import { MessagingProvidersBox } from "../../components/messaging-providers-box"; +import { trpcClient } from "../../modules/trpc/trpc-client"; + +const ConfigurationPage: NextPage = () => { + const { data, isLoading } = trpcClient.sendgridConfiguration.getConfigurations.useQuery(); + + return ( + + + + + + Configure Emails & Messages app to deliver Saleor Events webhooks to various messaging + providers + + + + + Manage providers configuration to connect Saleor events with 3rd party services. + + } + > + + + + ); +}; + +export default ConfigurationPage; diff --git a/apps/emails-and-messages/src/pages/configuration/sendgrid/edit/[id].tsx b/apps/emails-and-messages/src/pages/configuration/sendgrid/edit/[id].tsx new file mode 100644 index 0000000..e398310 --- /dev/null +++ b/apps/emails-and-messages/src/pages/configuration/sendgrid/edit/[id].tsx @@ -0,0 +1,38 @@ +import { Box, Text } from "@saleor/macaw-ui/next"; +import { NextPage } from "next"; +import { Breadcrumbs } from "../../../../components/breadcrumbs"; +import { trpcClient } from "../../../../modules/trpc/trpc-client"; +import { useRouter } from "next/router"; +import { BasicInformationSection } from "../../../../modules/sendgrid/ui/basic-information-section"; +import { DangerousSection } from "../../../../modules/sendgrid/ui/dangrous-section"; +import { SectionWithDescription } from "../../../../components/section-with-description"; +import { ApiConnectionSection } from "../../../../modules/sendgrid/ui/api-connection-section"; +import { SenderSection } from "../../../../modules/sendgrid/ui/sender-section"; + +const EditSendgridConfigurationPage: NextPage = () => { + const router = useRouter(); + const { id } = router.query; + + const { data: configuration } = trpcClient.sendgridConfiguration.getConfiguration.useQuery({ + id: id as string, + }); + + return ( + + + + + Connect Sendgrid with Saleor. + + + {!!configuration && } + {!!configuration && } + {!!configuration && } + {!!configuration && } + + ); +}; + +export default EditSendgridConfigurationPage; diff --git a/apps/emails-and-messages/src/pages/configuration/sendgrid/new.tsx b/apps/emails-and-messages/src/pages/configuration/sendgrid/new.tsx new file mode 100644 index 0000000..06caccd --- /dev/null +++ b/apps/emails-and-messages/src/pages/configuration/sendgrid/new.tsx @@ -0,0 +1,130 @@ +import { Box, Button, Input, Text } from "@saleor/macaw-ui/next"; +import { NextPage } from "next"; +import { Breadcrumbs } from "../../../components/breadcrumbs"; +import { SectionWithDescription } from "../../../components/section-with-description"; +import { BoxWithBorder } from "../../../components/box-with-border"; +import { defaultPadding } from "../../../components/ui-defaults"; +import { BoxFooter } from "../../../components/box-footer"; +import { trpcClient } from "../../../modules/trpc/trpc-client"; +import { useDashboardNotification } from "@saleor/apps-shared/src/use-dashboard-notification"; +import { Controller, useForm } from "react-hook-form"; +import { SendgridCreateConfigurationSchemaType } from "../../../modules/sendgrid/configuration/sendgrid-config-input-schema"; + +const NewSendgridConfigurationPage: NextPage = () => { + const { notifySuccess, notifyError } = useDashboardNotification(); + + const { handleSubmit, control, setError } = useForm(); + + const { mutate: createConfiguration } = + trpcClient.sendgridConfiguration.createConfiguration.useMutation({ + onSuccess: async (data, variables) => { + notifySuccess("Configuration saved"); + // TODO: redirect to configuration details based on id + }, + onError(error) { + let isFieldErrorSet = false; + const fieldErrors = error.data?.zodError?.fieldErrors || {}; + for (const fieldName in fieldErrors) { + for (const message of fieldErrors[fieldName] || []) { + isFieldErrorSet = true; + setError(fieldName as keyof SendgridCreateConfigurationSchemaType, { + type: "manual", + message, + }); + } + } + const formErrors = error.data?.zodError?.formErrors || []; + const formErrorMessage = formErrors.length ? formErrors.join("\n") : undefined; + + notifyError( + "Could not save the configuration", + isFieldErrorSet ? "Submitted form contain errors" : "Error saving configuration", + formErrorMessage + ); + }, + }); + + return ( + + + + + Connect Sendgrid with Saleor. + + + + Provide unique name for your configuration - you can create more than one. For example - + production and development. Then, pass your API Key. Obtain it here. + + } + > + +
{ + createConfiguration({ + ...data, + }); + })} + > + + ( + + )} + /> + ( + // TODO: add validation + + )} + /> + + + + +
+
+
+
+ ); +}; + +export default NewSendgridConfigurationPage; diff --git a/apps/emails-and-messages/src/pages/index.tsx b/apps/emails-and-messages/src/pages/index.tsx index be3b801..2955309 100644 --- a/apps/emails-and-messages/src/pages/index.tsx +++ b/apps/emails-and-messages/src/pages/index.tsx @@ -3,7 +3,6 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge"; import { useEffect } from "react"; import { useIsMounted } from "usehooks-ts"; import { useRouter } from "next/router"; -import { LinearProgress } from "@material-ui/core"; import { isInIframe } from "../lib/is-in-iframe"; import { appName } from "../const"; @@ -14,12 +13,12 @@ const IndexPage: NextPage = () => { useEffect(() => { if (isMounted() && appBridgeState?.ready) { - replace("/configuration/channels"); + replace("/configuration"); } }, [isMounted, appBridgeState?.ready, replace]); if (isInIframe()) { - return ; + return

Loading

; } return ( diff --git a/apps/emails-and-messages/src/styles/globals.css b/apps/emails-and-messages/src/styles/globals.css index 3a624b6..94c864c 100644 --- a/apps/emails-and-messages/src/styles/globals.css +++ b/apps/emails-and-messages/src/styles/globals.css @@ -1,21 +1,16 @@ body { - font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, - "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - color: #111; + color: var(--mu-colors-foreground-text-neutral-plain); + padding: 0; + margin: 0; + background: var(--mu-colors-background-plain); } code { - background: #f6f8fa; - border: 1px solid #eaeaea; - border-radius: 5px; display: inline-block; - margin-top: 10px; - padding: 0.75rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; + letter-spacing: 0.1em; + color: var(--mu-colors-foreground-text-neutral-subdued); } -code::before { - content: "$ "; - opacity: 0.6; +a { + text-decoration: none; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b2000c..d91023a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -325,7 +325,7 @@ importers: '@monaco-editor/react': ^4.4.6 '@saleor/app-sdk': 0.37.3 '@saleor/apps-shared': workspace:* - '@saleor/macaw-ui': 0.8.0-pre.72 + '@saleor/macaw-ui': 0.8.0-pre.76 '@sendgrid/client': ^7.7.0 '@sendgrid/mail': ^7.7.0 '@tanstack/react-query': ^4.24.4 @@ -375,7 +375,7 @@ importers: '@monaco-editor/react': 4.4.6_biqbaboplfbrettd7655fr4n2y '@saleor/app-sdk': 0.37.3_yucv4tfv7v7nrkw2uguegj6e7e '@saleor/apps-shared': link:../../packages/shared - '@saleor/macaw-ui': 0.8.0-pre.72_5ndqzdd6t4rivxsukjv3i3ak2q + '@saleor/macaw-ui': 0.8.0-pre.76_5ndqzdd6t4rivxsukjv3i3ak2q '@sendgrid/client': 7.7.0 '@sendgrid/mail': 7.7.0 '@tanstack/react-query': 4.24.4_biqbaboplfbrettd7655fr4n2y @@ -4804,8 +4804,8 @@ packages: - '@types/react' dev: false - /@saleor/macaw-ui/0.8.0-pre.72_5ndqzdd6t4rivxsukjv3i3ak2q: - resolution: {integrity: sha512-9lcFkzf81q9Mxjqd00rWUUvom26YK3WCu8GCcmpqcEFu723/H76hxg2/LUd2cpqARavS1FgO+Vri7jkxkSz7sQ==} + /@saleor/macaw-ui/0.8.0-pre.76_5ndqzdd6t4rivxsukjv3i3ak2q: + resolution: {integrity: sha512-z5zlgdiLcJTR4al4FP6Z3JBzcH1VWQQRrUVH/TraqvHfIxC5XCPz1ZSve1/KUyXafbjUaM0ih81B9vqfipbXRA==} engines: {node: '>=16 <19', pnpm: '>=8'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0