From 1da51639fa0ab5997b08cf35069e401faa56b954 Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Thu, 6 Apr 2023 12:56:44 +0200 Subject: [PATCH] Improve CMS app UX & UI (#349) * Install apps-shared, render null if app is loading in the iframe Redesign the app Add notification toasts and imrpve instructions Change channels list to select Replace lists with select * fix build * Fix build --- .changeset/rare-crews-guess.md | 5 + apps/cms/next.config.js | 1 + apps/cms/package.json | 3 +- .../src/lib/cms/client/clients-operations.ts | 5 +- .../src/lib/cms/client/metadata-execution.ts | 2 - apps/cms/src/lib/cms/client/settings.ts | 1 - apps/cms/src/lib/metadata.ts | 2 +- .../ui/channel-configuration-form.tsx | 17 +- .../channels/ui/channel-configuration.tsx | 3 +- .../channels/ui/channels-list-items.tsx | 6 +- .../src/modules/channels/ui/channels-list.tsx | 21 +-- .../modules/channels/ui/channels-select.tsx | 41 +++++ apps/cms/src/modules/channels/ui/channels.tsx | 53 +++++-- .../modules/channels/ui/hooks/useChannels.ts | 12 +- .../channels/ui/hooks/useChannelsFetch.ts | 6 +- .../ui/hooks/useProviderInstances.ts | 2 - .../ui/hooks/useProviderInstancesFetch.ts | 2 + .../provider-instance-configuration-form.tsx | 8 +- .../ui/provider-instance-configuration.tsx | 148 +++++++++++------- .../ui/provider-instances-list-items.tsx | 2 - .../ui/provider-instances-list.tsx | 63 +++----- .../ui/provider-instances.tsx | 91 ++++++++--- apps/cms/src/modules/providers/config.ts | 1 - apps/cms/src/modules/ui/app-container.tsx | 3 +- apps/cms/src/modules/ui/app-grid.tsx | 2 +- apps/cms/src/modules/ui/app-paper.tsx | 7 +- apps/cms/src/modules/ui/app-tabs.tsx | 44 +++++- apps/cms/src/modules/ui/instructions.tsx | 66 -------- .../api/webhooks/product-variant-deleted.ts | 1 - apps/cms/src/pages/channels.tsx | 5 - apps/cms/src/pages/home.tsx | 78 +++++++++ apps/cms/src/pages/index.tsx | 12 +- apps/cms/src/pages/providers.tsx | 5 - apps/cms/src/styles/globals.css | 8 +- pnpm-lock.yaml | 2 + 35 files changed, 436 insertions(+), 292 deletions(-) create mode 100644 .changeset/rare-crews-guess.md create mode 100644 apps/cms/src/modules/channels/ui/channels-select.tsx delete mode 100644 apps/cms/src/modules/ui/instructions.tsx create mode 100644 apps/cms/src/pages/home.tsx diff --git a/.changeset/rare-crews-guess.md b/.changeset/rare-crews-guess.md new file mode 100644 index 0000000..6f71c06 --- /dev/null +++ b/.changeset/rare-crews-guess.md @@ -0,0 +1,5 @@ +--- +"saleor-app-cms": minor +--- + +Redesigned the app to better match new Dashboard diff --git a/apps/cms/next.config.js b/apps/cms/next.config.js index 58c02e3..964c407 100644 --- a/apps/cms/next.config.js +++ b/apps/cms/next.config.js @@ -14,6 +14,7 @@ module.exports = { disableServerWebpackPlugin: !isSentryPropertiesInEnvironment, disableClientWebpackPlugin: !isSentryPropertiesInEnvironment, }, + transpilePackages: ["@saleor/apps-shared"], }; module.exports = withSentryConfig(module.exports, { silent: true }, { hideSourcemaps: true }); diff --git a/apps/cms/package.json b/apps/cms/package.json index cff13bf..03faefc 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -38,7 +38,8 @@ "usehooks-ts": "^2.9.1", "uuid": "^9.0.0", "vite": "^4.1.4", - "zod": "^3.19.1" + "zod": "^3.19.1", + "@saleor/apps-shared": "workspace:*" }, "devDependencies": { "@graphql-codegen/cli": "2.13.3", diff --git a/apps/cms/src/lib/cms/client/clients-operations.ts b/apps/cms/src/lib/cms/client/clients-operations.ts index e51a173..cf88712 100644 --- a/apps/cms/src/lib/cms/client/clients-operations.ts +++ b/apps/cms/src/lib/cms/client/clients-operations.ts @@ -1,14 +1,13 @@ import { NextWebhookApiHandler } from "@saleor/app-sdk/handlers/next"; -import { createClient } from "../../graphql"; import { createSettingsManager } from "../../metadata"; import { getOperationType } from "./operations"; import { getChannelsSettings, - getProviderInstancesSettings, getProductVariantProviderInstancesToAlter, + getProviderInstancesSettings, } from "./settings"; import { providersSchemaSet } from "../config"; -import { cmsProviders, CMSProvider } from "../providers"; +import { CMSProvider, cmsProviders } from "../providers"; import { CmsClientOperations } from "../types"; import { logger as pinoLogger } from "../../logger"; import { getCmsIdFromSaleorItemKey } from "./metadata"; diff --git a/apps/cms/src/lib/cms/client/metadata-execution.ts b/apps/cms/src/lib/cms/client/metadata-execution.ts index a66a682..193fc50 100644 --- a/apps/cms/src/lib/cms/client/metadata-execution.ts +++ b/apps/cms/src/lib/cms/client/metadata-execution.ts @@ -1,8 +1,6 @@ -import { AuthData } from "@saleor/app-sdk/APL"; import { NextWebhookApiHandler } from "@saleor/app-sdk/handlers/next"; import { DeleteMetadataDocument, - ProductVariantUpdatedWebhookPayloadFragment, UpdateMetadataDocument, WebhookProductVariantFragment, } from "../../../../generated/graphql"; diff --git a/apps/cms/src/lib/cms/client/settings.ts b/apps/cms/src/lib/cms/client/settings.ts index f16f0b6..3506bce 100644 --- a/apps/cms/src/lib/cms/client/settings.ts +++ b/apps/cms/src/lib/cms/client/settings.ts @@ -1,6 +1,5 @@ import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager"; import { CMSSchemaChannels, CMSSchemaProviderInstances } from "../config"; -import { createCmsKeyForSaleorItem, getCmsIdFromSaleorItemKey } from "./metadata"; export const getChannelsSettings = async (settingsManager: EncryptedMetadataManager) => { const channelsSettings = await settingsManager.get("channels"); diff --git a/apps/cms/src/lib/metadata.ts b/apps/cms/src/lib/metadata.ts index b3104cf..89d57c0 100644 --- a/apps/cms/src/lib/metadata.ts +++ b/apps/cms/src/lib/metadata.ts @@ -1,4 +1,4 @@ -import { MetadataEntry, EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager"; +import { EncryptedMetadataManager, MetadataEntry } from "@saleor/app-sdk/settings-manager"; import { Client } from "urql"; import { diff --git a/apps/cms/src/modules/channels/ui/channel-configuration-form.tsx b/apps/cms/src/modules/channels/ui/channel-configuration-form.tsx index de94fad..62604fe 100644 --- a/apps/cms/src/modules/channels/ui/channel-configuration-form.tsx +++ b/apps/cms/src/modules/channels/ui/channel-configuration-form.tsx @@ -1,18 +1,17 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import { Checkbox, FormControl, FormControlLabel, Switch, Typography } from "@material-ui/core"; +import { Checkbox, FormControl, Typography } from "@material-ui/core"; import { - makeStyles, + Button, List, ListBody, + ListFooter, ListHeader, ListItem, ListItemCell, - ListFooter, - Button, + makeStyles, } from "@saleor/macaw-ui"; import React from "react"; -import { Controller, useController, useForm } from "react-hook-form"; -import { z } from "zod"; +import { useForm } from "react-hook-form"; import { channelSchema, ChannelSchema, @@ -39,6 +38,10 @@ const useStyles = makeStyles((theme) => { justifyContent: "flex-end", padding: theme.spacing(2, 4), }, + form: { + border: `1px solid hsla(212, 44%, 13%, 0.08)`, + borderRadius: 8, + }, }; }); @@ -87,7 +90,7 @@ export const ChannelConfigurationForm = ({ const errors = formState.errors; return ( -
+ {!!Object.entries(errors).length && ( Error validating form diff --git a/apps/cms/src/modules/channels/ui/channel-configuration.tsx b/apps/cms/src/modules/channels/ui/channel-configuration.tsx index 6d1b4fd..a686d2b 100644 --- a/apps/cms/src/modules/channels/ui/channel-configuration.tsx +++ b/apps/cms/src/modules/channels/ui/channel-configuration.tsx @@ -1,10 +1,9 @@ import { AppPaper } from "../../ui/app-paper"; -import { FormControlLabel, Grid, Paper, Radio, RadioGroup, Typography } from "@material-ui/core"; +import { Grid, Paper, Typography } from "@material-ui/core"; import { Skeleton } from "@material-ui/lab"; import { ChannelConfigurationForm } from "./channel-configuration-form"; import { MergedChannelSchema, - ProvidersSchema, SingleChannelSchema, SingleProviderSchema, } from "../../../lib/cms/config"; diff --git a/apps/cms/src/modules/channels/ui/channels-list-items.tsx b/apps/cms/src/modules/channels/ui/channels-list-items.tsx index cac565d..cdb257d 100644 --- a/apps/cms/src/modules/channels/ui/channels-list-items.tsx +++ b/apps/cms/src/modules/channels/ui/channels-list-items.tsx @@ -1,5 +1,5 @@ -import { makeStyles } from "@saleor/macaw-ui"; import { + makeStyles, OffsettedList, OffsettedListBody, OffsettedListHeader, @@ -7,9 +7,7 @@ import { OffsettedListItemCell, } from "@saleor/macaw-ui"; import clsx from "clsx"; -import { ChannelFragment } from "../../../../generated/graphql"; -import { MergedChannelSchema, SingleChannelSchema } from "../../../lib/cms/config"; -import { ProviderIcon } from "../../provider-instances/ui/provider-icon"; +import { MergedChannelSchema } from "../../../lib/cms/config"; const useStyles = makeStyles((theme) => { return { diff --git a/apps/cms/src/modules/channels/ui/channels-list.tsx b/apps/cms/src/modules/channels/ui/channels-list.tsx index 91b4834..c384f39 100644 --- a/apps/cms/src/modules/channels/ui/channels-list.tsx +++ b/apps/cms/src/modules/channels/ui/channels-list.tsx @@ -1,25 +1,14 @@ -import { Grid } from "@material-ui/core"; import { Skeleton } from "@material-ui/lab"; -import { ChannelFragment } from "../../../../generated/graphql"; -import { MergedChannelSchema, SingleChannelSchema } from "../../../lib/cms"; +import { MergedChannelSchema } from "../../../lib/cms"; import { AppPaper } from "../../ui/app-paper"; -import { ChannelsListItems } from "./channels-list-items"; + import { ChannelsErrors, ChannelsLoading } from "./types"; +import { ChannelsSelect } from "./channels-select"; const ChannelsListSkeleton = () => { return ( - - - - - - - - - - - + ); }; @@ -48,7 +37,7 @@ export const ChannelsList = ({ } return ( - void; +} + +export const ChannelsSelect = ({ + channels, + activeChannel, + setActiveChannel, + ...props +}: ChannelsListItemsProps) => { + console.log(activeChannel); + + return ( + + Select channel to configure + + + + ); +}; diff --git a/apps/cms/src/modules/channels/ui/channels.tsx b/apps/cms/src/modules/channels/ui/channels.tsx index f99ea6e..395a2c8 100644 --- a/apps/cms/src/modules/channels/ui/channels.tsx +++ b/apps/cms/src/modules/channels/ui/channels.tsx @@ -1,12 +1,22 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { MergedChannelSchema } from "../../../lib/cms/config"; import { useProviderInstances } from "../../provider-instances/ui/hooks/useProviderInstances"; -import { Instructions } from "../../ui/instructions"; import { ChannelConfiguration } from "./channel-configuration"; import { ChannelsList } from "./channels-list"; import { useChannels } from "./hooks/useChannels"; +import { AppTabs } from "../../ui/app-tabs"; +import { makeStyles } from "@saleor/macaw-ui"; + +const useStyles = makeStyles({ + wrapper: { + display: "flex", + flexDirection: "column", + gap: 16, + }, +}); export const Channels = () => { + const styles = useStyles(); const { channels, saveChannel, loading, errors } = useChannels(); const { providerInstances } = useProviderInstances(); @@ -20,23 +30,32 @@ export const Channels = () => { const activeChannel = channels.find((channel) => channel.channelSlug === activeChannelSlug); + useEffect(() => { + if (!activeChannelSlug && channels.length > 0) { + setActiveChannelSlug(channels[0].channelSlug); + } + }, [channels]); + return ( <> - - - + + +
+ + +
); }; diff --git a/apps/cms/src/modules/channels/ui/hooks/useChannels.ts b/apps/cms/src/modules/channels/ui/hooks/useChannels.ts index 79f3381..c1e647a 100644 --- a/apps/cms/src/modules/channels/ui/hooks/useChannels.ts +++ b/apps/cms/src/modules/channels/ui/hooks/useChannels.ts @@ -3,8 +3,10 @@ import { MergedChannelSchema, SingleChannelSchema } from "../../../../lib/cms/co import { ChannelsErrors, ChannelsLoading } from "../types"; import { useChannelsQuery } from "../../../../../generated/graphql"; import { useIsMounted } from "usehooks-ts"; +import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge"; export const useChannels = () => { + const { appBridge } = useAppBridge(); const isMounted = useIsMounted(); const [channelsQueryData, channelsQueryOpts] = useChannelsQuery({ pause: !isMounted, @@ -20,7 +22,15 @@ export const useChannels = () => { const saveChannel = (channelToSave: SingleChannelSchema) => { console.log("saveChannel", channelToSave); - saveChannelFetch(channelToSave); + saveChannelFetch(channelToSave).then(() => { + appBridge?.dispatch( + actions.Notification({ + title: "Success", + status: "success", + text: "Configuration saved", + }) + ); + }); }; const loading: ChannelsLoading = { diff --git a/apps/cms/src/modules/channels/ui/hooks/useChannelsFetch.ts b/apps/cms/src/modules/channels/ui/hooks/useChannelsFetch.ts index fde5f62..073c204 100644 --- a/apps/cms/src/modules/channels/ui/hooks/useChannelsFetch.ts +++ b/apps/cms/src/modules/channels/ui/hooks/useChannelsFetch.ts @@ -1,10 +1,6 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge"; import React from "react"; -import { - CMSSchemaChannels, - cmsSchemaChannels, - SingleChannelSchema, -} from "../../../../lib/cms/config"; +import { CMSSchemaChannels, SingleChannelSchema } from "../../../../lib/cms/config"; import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const"; import { ChannelsApiResponse } from "../../../../pages/api/channels"; diff --git a/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstances.ts b/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstances.ts index ffada21..7f2c22c 100644 --- a/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstances.ts +++ b/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstances.ts @@ -13,8 +13,6 @@ export const useProviderInstances = () => { } = useProviderInstancesFetch(); const saveProviderInstance = async (providerInstanceToSave: SingleProviderSchema) => { - console.log("saveProviderInstance", providerInstanceToSave); - return await saveProviderInstanceFetch(providerInstanceToSave); }; diff --git a/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstancesFetch.ts b/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstancesFetch.ts index eeaee43..ed40a43 100644 --- a/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstancesFetch.ts +++ b/apps/cms/src/modules/provider-instances/ui/hooks/useProviderInstancesFetch.ts @@ -55,6 +55,8 @@ export const useProviderInstancesFetch = () => { }; const saveProviderInstance = async (instance: SingleProviderSchema) => { + console.log(instance); + try { setIsSaving(true); const response = await fetch("/api/provider-instances", { diff --git a/apps/cms/src/modules/provider-instances/ui/provider-instance-configuration-form.tsx b/apps/cms/src/modules/provider-instances/ui/provider-instance-configuration-form.tsx index ee567d9..9a46eb3 100644 --- a/apps/cms/src/modules/provider-instances/ui/provider-instance-configuration-form.tsx +++ b/apps/cms/src/modules/provider-instances/ui/provider-instance-configuration-form.tsx @@ -2,16 +2,13 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Grid, TextField, Typography } from "@material-ui/core"; import { Button, makeStyles } from "@saleor/macaw-ui"; import React from "react"; -import ReactMarkdown from "react-markdown"; import { Path, useForm } from "react-hook-form"; -import { z } from "zod"; import { - providersConfig, CMSProviderSchema, - providersSchemaSet, + providersConfig, ProvidersSchema, + providersSchemaSet, SingleProviderSchema, - ProviderInstanceSchema, } from "../../../lib/cms/config"; import { Provider } from "../../providers/config"; import { AppMarkdownText } from "../../ui/app-markdown-text"; @@ -68,7 +65,6 @@ export const ProviderInstanceConfigurationForm = ({ textAlign: "center", margin: theme.spacing(1, 0, 3, 0), }, + newProviderButton: { + margin: "30px auto", + }, + newProviderContainer: { + display: "flex", + justifyContent: "center", + }, + box: { + border: `1px solid hsla(212, 44%, 13%, 0.08)`, + borderRadius: 8, + padding: 20, + }, })); const ProviderInstanceConfigurationSkeleton = () => { @@ -82,6 +94,7 @@ interface ProviderInstanceConfigurationProps { deleteProviderInstance: (providerInstance: SingleProviderSchema) => any; loading: ProvidersLoading; errors: ProvidersErrors; + onNewProviderRequest(): void; } export const ProviderInstanceConfiguration = ({ @@ -90,6 +103,7 @@ export const ProviderInstanceConfiguration = ({ saveProviderInstance, deleteProviderInstance, loading, + onNewProviderRequest, errors, }: ProviderInstanceConfigurationProps) => { const [selectedProvider, setSelectedProvider] = React.useState( @@ -120,70 +134,86 @@ export const ProviderInstanceConfiguration = ({ Please select a provider configuration or add new one. +
+ +
); } return ( - - {errors.fetching && ( - - Error fetching available providers - - )} - {errors.saving && ( - - Error saving provider configuration - - )} - {!!newProviderInstance && ( - - Add new configuration - - )} - - - {Object.entries(providersConfig).map(([name, config]) => ( - - } - label={ -
- {`${config.label} - {config.label} -
- } - labelPlacement="top" - aria-label={config.label} - /> -
- ))} -
-
- {selectedProvider ? ( - <> -
- - - ) : ( - <> -
- - Please select a provider. + +
+ {errors.fetching && ( + + Error fetching available providers - - )} + )} + {errors.saving && ( + + Error saving provider configuration + + )} + {!!newProviderInstance && ( + + Add new configuration + + )} + + + {Object.entries(providersConfig).map(([name, config]) => ( + + } + label={ +
+ {`${config.label} + {config.label} +
+ } + labelPlacement="top" + aria-label={config.label} + /> +
+ ))} +
+
+ {selectedProvider ? ( + <> +
+ + + ) : ( + <> +
+ + Please select a provider. + + + )} +
); }; diff --git a/apps/cms/src/modules/provider-instances/ui/provider-instances-list-items.tsx b/apps/cms/src/modules/provider-instances/ui/provider-instances-list-items.tsx index e7738c9..6711d4f 100644 --- a/apps/cms/src/modules/provider-instances/ui/provider-instances-list-items.tsx +++ b/apps/cms/src/modules/provider-instances/ui/provider-instances-list-items.tsx @@ -6,11 +6,9 @@ import { OffsettedListItem, OffsettedListItemCell, } from "@saleor/macaw-ui"; -import Image from "next/image"; import clsx from "clsx"; import React from "react"; import { SingleProviderSchema } from "../../../lib/cms/config"; -import { getProviderByName } from "../../providers/config"; import { ProviderIcon } from "./provider-icon"; const useStyles = makeStyles((theme) => { diff --git a/apps/cms/src/modules/provider-instances/ui/provider-instances-list.tsx b/apps/cms/src/modules/provider-instances/ui/provider-instances-list.tsx index 2a14e4f..cdca54a 100644 --- a/apps/cms/src/modules/provider-instances/ui/provider-instances-list.tsx +++ b/apps/cms/src/modules/provider-instances/ui/provider-instances-list.tsx @@ -1,20 +1,9 @@ -import { Grid } from "@material-ui/core"; -import { Add } from "@material-ui/icons"; +import { FormControl, Grid, InputLabel, MenuItem, Select } from "@material-ui/core"; import { Skeleton } from "@material-ui/lab"; -import { Button, makeStyles } from "@saleor/macaw-ui"; import { SingleProviderSchema } from "../../../lib/cms/config"; import { AppPaper } from "../../ui/app-paper"; -import { ProviderInstancesListItems, ProviderItem } from "./provider-instances-list-items"; -import { ProvidersErrors, ProvidersLoading } from "./types"; -const useStyles = makeStyles((theme) => { - return { - button: { - padding: theme.spacing(1, 2), - justifyContent: "flex-start", - }, - }; -}); +import { ProvidersErrors, ProvidersLoading } from "./types"; const ProviderInstancesListSkeleton = () => { return ( @@ -44,7 +33,7 @@ interface ProviderInstancesListProps { errors: ProvidersErrors; } -export const ProviderInstancesList = ({ +export const ProviderInstancesSelect = ({ providerInstances, activeProviderInstance, newProviderInstance, @@ -53,8 +42,6 @@ export const ProviderInstancesList = ({ loading, errors, }: ProviderInstancesListProps) => { - const styles = useStyles(); - const handleSetActiveProviderInstance = (providerInstance: SingleProviderSchema) => { setActiveProviderInstance(providerInstance); }; @@ -67,27 +54,29 @@ export const ProviderInstancesList = ({ return
Error loading providers
; } + if (providerInstances.length === 0 || !activeProviderInstance) { + return null; + } + return ( - - {!!providerInstances.length && ( - - - - )} - - - - + + Select Provider to configure + + + ); }; diff --git a/apps/cms/src/modules/provider-instances/ui/provider-instances.tsx b/apps/cms/src/modules/provider-instances/ui/provider-instances.tsx index 4620133..7d2f117 100644 --- a/apps/cms/src/modules/provider-instances/ui/provider-instances.tsx +++ b/apps/cms/src/modules/provider-instances/ui/provider-instances.tsx @@ -1,19 +1,36 @@ -import { ProviderInstancesList } from "./provider-instances-list"; -import { Instructions } from "../../ui/instructions"; import { ProviderInstanceConfiguration } from "./provider-instance-configuration"; -import { providersConfig, ProvidersSchema, SingleProviderSchema } from "../../../lib/cms/config"; +import { SingleProviderSchema } from "../../../lib/cms/config"; import { useEffect, useState } from "react"; import { useProviderInstances } from "./hooks/useProviderInstances"; +import { AppTabs } from "../../ui/app-tabs"; +import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge"; +import { Button, makeStyles } from "@saleor/macaw-ui"; +import { ProviderInstancesSelect } from "./provider-instances-list"; +import { Add } from "@material-ui/icons"; + +const useStyles = makeStyles({ + wrapper: { + display: "flex", + flexDirection: "column", + gap: 16, + }, +}); export const ProviderInstances = () => { + const styles = useStyles(); + const { appBridge } = useAppBridge(); const { providerInstances, saveProviderInstance, deleteProviderInstance, loading, errors } = useProviderInstances(); - const [activeProviderInstanceId, setActiveProviderInstanceId] = useState( - providerInstances.length ? providerInstances[0].id : null - ); + const [activeProviderInstanceId, setActiveProviderInstanceId] = useState(null); const [newProviderInstance, setNewProviderInstance] = useState(null); + useEffect(() => { + if (providerInstances.length) { + setActiveProviderInstanceId(providerInstances[0].id); + } + }, [providerInstances]); + const handleSetActiveProviderInstance = (providerInstance: SingleProviderSchema | null) => { setActiveProviderInstanceId(providerInstance?.id || null); @@ -31,6 +48,14 @@ export const ProviderInstances = () => { const handleSaveProviderInstance = async (providerInstance: SingleProviderSchema) => { const savedProviderInstance = await saveProviderInstance(providerInstance); + appBridge?.dispatch( + actions.Notification({ + title: "Success", + status: "success", + text: "Configuration saved", + }) + ); + if (newProviderInstance) { setNewProviderInstance(null); } @@ -52,24 +77,42 @@ export const ProviderInstances = () => { return ( <> - - - + + +
+ {!newProviderInstance && ( + + )} + +
+
+ {providerInstances.length > 0 && ( + + )} +
); }; diff --git a/apps/cms/src/modules/providers/config.ts b/apps/cms/src/modules/providers/config.ts index a445949..c12390f 100644 --- a/apps/cms/src/modules/providers/config.ts +++ b/apps/cms/src/modules/providers/config.ts @@ -1,4 +1,3 @@ -import { z } from "zod"; import { ContentfulIcon, DatocmsIcon, StrapiIcon } from "../../assets"; export const CMS_ID_KEY = "cmsId"; diff --git a/apps/cms/src/modules/ui/app-container.tsx b/apps/cms/src/modules/ui/app-container.tsx index ff29804..6dda601 100644 --- a/apps/cms/src/modules/ui/app-container.tsx +++ b/apps/cms/src/modules/ui/app-container.tsx @@ -2,8 +2,7 @@ import { makeStyles } from "@saleor/macaw-ui"; export const useStyles = makeStyles({ root: { - maxWidth: 1180, - margin: "0 auto", + margin: "12px auto", }, }); diff --git a/apps/cms/src/modules/ui/app-grid.tsx b/apps/cms/src/modules/ui/app-grid.tsx index fd3efdd..5054b64 100644 --- a/apps/cms/src/modules/ui/app-grid.tsx +++ b/apps/cms/src/modules/ui/app-grid.tsx @@ -4,7 +4,7 @@ import { PropsWithChildren } from "react"; export const useStyles = makeStyles({ root: { display: "grid", - gridTemplateColumns: "280px auto 280px", + gridTemplateColumns: "280px minmax(400px, 600px) 280px", alignItems: "start", gap: 32, }, diff --git a/apps/cms/src/modules/ui/app-paper.tsx b/apps/cms/src/modules/ui/app-paper.tsx index 79cae94..9616461 100644 --- a/apps/cms/src/modules/ui/app-paper.tsx +++ b/apps/cms/src/modules/ui/app-paper.tsx @@ -1,6 +1,7 @@ -import { Paper } from "@material-ui/core"; +import { Paper, PaperProps } from "@material-ui/core"; import { makeStyles } from "@saleor/macaw-ui"; import React from "react"; +import clsx from "clsx"; const useStyles = makeStyles({ root: { @@ -8,10 +9,10 @@ const useStyles = makeStyles({ }, }); -export const AppPaper = ({ children }: { children: React.ReactNode }) => { +export const AppPaper = ({ children, className, ...props }: PaperProps) => { const styles = useStyles(); return ( - + {children} ); diff --git a/apps/cms/src/modules/ui/app-tabs.tsx b/apps/cms/src/modules/ui/app-tabs.tsx index 2f59cef..0bdf578 100644 --- a/apps/cms/src/modules/ui/app-tabs.tsx +++ b/apps/cms/src/modules/ui/app-tabs.tsx @@ -1,13 +1,34 @@ -import { makeStyles, PageTab, PageTabs } from "@saleor/macaw-ui"; +import { makeStyles } from "@saleor/macaw-ui"; import { useRouter } from "next/router"; +import clsx from "clsx"; +import { ButtonBase, Typography } from "@material-ui/core"; const useStyles = makeStyles({ tabs: { - margin: "16px 0", + display: "flex", + flexDirection: "column", + }, + button: { + background: "#fff", + border: "none", + fontSize: 14, + height: 50, + textAlign: "left", + cursor: "pointer", + borderRadius: 8, + padding: "0 20px", + + justifyContent: "flex-start", + }, + active: { + border: `1px solid hsla(212, 14%, 77%, 1)`, }, }); const tabs = { + home: { + label: "Home", + }, channels: { label: "Channels", }, @@ -32,11 +53,20 @@ export const AppTabs = ({ activeTab }: AppTabsProps) => { return (
- - {Object.entries(tabs).map(([key, config]) => ( - - ))} - + {Object.entries(tabs).map(([key, config]) => ( + { + handleTabChange(key); + }} + > + {config.label} + + ))}
); }; diff --git a/apps/cms/src/modules/ui/instructions.tsx b/apps/cms/src/modules/ui/instructions.tsx deleted file mode 100644 index 6c7f4b0..0000000 --- a/apps/cms/src/modules/ui/instructions.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Typography } from "@material-ui/core"; -import { makeStyles } from "@saleor/macaw-ui"; -import { AppLink } from "./app-link"; -import { AppPaper } from "./app-paper"; -import { AppTabNavButton } from "./app-tab-nav-button"; - -const useStyles = makeStyles(() => ({ - root: { - display: "flex", - flexDirection: "column", - gap: "16px", - }, - list: { - paddingLeft: "16px", - margin: 0, - color: "inherit", - }, -})); - -export const Instructions = () => { - const styles = useStyles(); - return ( - -
- - Use external service for cms product data syncronization - - -
    -
  1. - In the CMS App, go to the Providers{" "} - tab to add a configuration of your provider. Click Add configuration, and - select the cms provider you want to use. Fill in the configuration form and hit{" "} - Save. -
  2. -
  3. - Go to your CMS website and prepare product variant model shape with: -
      -
    • - string fields: saleor_id, name, product_id,{" "} - product_name, product_slug, -
    • -
    • - JSON fileds: channels. -
    • -
    -
  4. -
  5. - Go to the Channels tab. Select a - channel. Select the CMS configurations you want to sync product variants data against - available in this channel and hit Save. -
  6. -
  7. - Saleor will now use the channel's configured CMS provider for product variant - syncronisation once it is created, updated or deleted. -
  8. -
  9. - To see the effect, go to Products. Add, update or - delete channel listing for any product variant. -
  10. -
-
-
-
- ); -}; diff --git a/apps/cms/src/pages/api/webhooks/product-variant-deleted.ts b/apps/cms/src/pages/api/webhooks/product-variant-deleted.ts index d35bb55..0b9b20d 100644 --- a/apps/cms/src/pages/api/webhooks/product-variant-deleted.ts +++ b/apps/cms/src/pages/api/webhooks/product-variant-deleted.ts @@ -13,7 +13,6 @@ import { } from "../../../lib/cms/client"; import { logger as pinoLogger } from "../../../lib/logger"; import { createClient } from "../../../lib/graphql"; -import { fetchProductVariantMetadata } from "../../../lib/metadata"; export const config = { api: { diff --git a/apps/cms/src/pages/channels.tsx b/apps/cms/src/pages/channels.tsx index 6690562..a8480ea 100644 --- a/apps/cms/src/pages/channels.tsx +++ b/apps/cms/src/pages/channels.tsx @@ -1,6 +1,4 @@ -import { AppTabs } from "../modules/ui/app-tabs"; import { Channels } from "../modules/channels/ui/channels"; -import { AppContainer } from "../modules/ui/app-container"; import { AppLayout } from "../modules/ui/app-layout"; import { NextPageWithLayout } from "./_app"; import { ReactElement } from "react"; @@ -10,9 +8,6 @@ const Page: NextPageWithLayout = () => ; Page.getLayout = function getLayout(page: ReactElement) { return (
- - - {page}
); diff --git a/apps/cms/src/pages/home.tsx b/apps/cms/src/pages/home.tsx new file mode 100644 index 0000000..6d3fc29 --- /dev/null +++ b/apps/cms/src/pages/home.tsx @@ -0,0 +1,78 @@ +import { AppTabs } from "../modules/ui/app-tabs"; +import { AppLayout } from "../modules/ui/app-layout"; +import { NextPageWithLayout } from "./_app"; +import { ReactElement } from "react"; + +import { AppTabNavButton } from "../modules/ui/app-tab-nav-button"; +import { makeStyles } from "@saleor/macaw-ui"; +import { Box, Typography } from "@material-ui/core"; + +const useStyles = makeStyles({ + section: { + border: `1px solid hsla(330, 5%, 91%, 1)`, // todo macaw + padding: 20, + borderRadius: 8, + }, + dataModelList: { + listStyle: "none", + margin: 0, + padding: 0, + "& li": { + margin: "1em 0", + }, + }, +}); + +const HomePage: NextPageWithLayout = () => { + const styles = useStyles(); + + return ( + <> + +
+ + Connect CMS +

+ Visit providers settings to configure + one of existing providers. Then, connect provider to each{" "} + channel. +

+
+
+ CMS Data Model setup +

CMS App requires your CMS data model to be configured with following structure:

+
    +
  • + saleor_id - string (Variant ID in Saleor) +
  • +
  • + name - string (Variant name in Saleor) +
  • +
  • + product_id - string (Product ID in Saleor) +
  • +
  • + product_name - string (Product name in Saleor) +
  • +
  • + product_slug - string (Product slug in Saleor) +
  • +
  • + channels - JSON (list of channels product belongs to) +
  • +
+
+
+ + ); +}; + +HomePage.getLayout = function getLayout(page: ReactElement) { + return ( +
+ {page} +
+ ); +}; + +export default HomePage; diff --git a/apps/cms/src/pages/index.tsx b/apps/cms/src/pages/index.tsx index 4a16ce5..c35ddcd 100644 --- a/apps/cms/src/pages/index.tsx +++ b/apps/cms/src/pages/index.tsx @@ -1,9 +1,9 @@ import { useAppBridge } from "@saleor/app-sdk/app-bridge"; import { NextPage } from "next"; -import dynamic from "next/dynamic"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useIsMounted } from "usehooks-ts"; +import { isInIframe } from "@saleor/apps-shared"; /** * This is page publicly accessible from your app. @@ -16,9 +16,13 @@ const IndexPage: NextPage = () => { useEffect(() => { if (isMounted() && appBridgeState?.ready) { - replace("/providers"); + replace("/home"); } - }, [isMounted, appBridgeState?.ready]); + }, [isMounted, appBridgeState?.ready, replace]); + + if (isInIframe()) { + return null; + } return (
diff --git a/apps/cms/src/pages/providers.tsx b/apps/cms/src/pages/providers.tsx index 6bfc120..c6c0854 100644 --- a/apps/cms/src/pages/providers.tsx +++ b/apps/cms/src/pages/providers.tsx @@ -1,6 +1,4 @@ -import { AppContainer } from "../modules/ui/app-container"; import { AppLayout } from "../modules/ui/app-layout"; -import { AppTabs } from "../modules/ui/app-tabs"; import { ProviderInstances } from "../modules/provider-instances/ui/provider-instances"; import { NextPageWithLayout } from "./_app"; import { ReactElement } from "react"; @@ -10,9 +8,6 @@ const Page: NextPageWithLayout = () => ; Page.getLayout = function getLayout(page: ReactElement) { return (
- - - {page}
); diff --git a/apps/cms/src/styles/globals.css b/apps/cms/src/styles/globals.css index ceefe02..f9d5e5c 100644 --- a/apps/cms/src/styles/globals.css +++ b/apps/cms/src/styles/globals.css @@ -10,13 +10,7 @@ code { border: 1px solid #eaeaea; border-radius: 5px; display: inline-block; - margin-top: 10px; - padding: 0.75rem; + padding: 0.2em 0.5em; font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace; } - -code::before { - content: "$ "; - opacity: 0.6; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6527169..6e34462 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,7 @@ importers: '@material-ui/icons': ^4.11.3 '@material-ui/lab': 4.0.0-alpha.61 '@saleor/app-sdk': 0.37.1 + '@saleor/apps-shared': workspace:* '@saleor/macaw-ui': ^0.6.7 '@sentry/nextjs': ^7.43.0 '@testing-library/react': ^13.4.0 @@ -76,6 +77,7 @@ importers: '@material-ui/icons': 4.11.3_x54wk6dsnsxe7g7vvfmytp77te '@material-ui/lab': 4.0.0-alpha.61_x54wk6dsnsxe7g7vvfmytp77te '@saleor/app-sdk': 0.37.1_ld2jel3hspngo3u5lti2kgl2sq + '@saleor/apps-shared': link:../../packages/shared '@saleor/macaw-ui': 0.6.7_pmlnlm755hlzzzocw2qhf3a34e '@sentry/nextjs': 7.43.0_next@13.2.4+react@18.2.0 '@urql/exchange-auth': 1.0.0_graphql@16.6.0