Add Payload CMS (#905)
This commit is contained in:
parent
45ed9fb444
commit
6f1c5c9436
23 changed files with 723 additions and 69 deletions
5
.changeset/light-bobcats-prove.md
Normal file
5
.changeset/light-bobcats-prove.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-cms-v2": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix styling of modal in the dark mode
|
5
.changeset/strong-pugs-cover.md
Normal file
5
.changeset/strong-pugs-cover.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-app-cms-v2": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added Payload CMS support.
|
|
@ -35,6 +35,7 @@
|
||||||
"p-ratelimit": "1.0.1",
|
"p-ratelimit": "1.0.1",
|
||||||
"pino": "^8.14.1",
|
"pino": "^8.14.1",
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
|
"qs": "6.11.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "4.0.10",
|
"react-error-boundary": "4.0.10",
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
|
"@types/qs": "^6.9.7",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"eslint": "8.46.0",
|
"eslint": "8.46.0",
|
||||||
|
|
|
@ -2,11 +2,11 @@ import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||||
import { Client } from "urql";
|
import { Client } from "urql";
|
||||||
|
|
||||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
|
||||||
|
|
||||||
export const createSettingsManager = (
|
export const createSettingsManager = (
|
||||||
client: Pick<Client, "query" | "mutation">,
|
client: Pick<Client, "query" | "mutation">,
|
||||||
appId: string,
|
appId: string,
|
||||||
): SettingsManager => {
|
): SettingsManager => {
|
||||||
|
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||||
|
|
||||||
return metadataManagerFactory.create(client, appId);
|
return metadataManagerFactory.create(client, appId);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { PayloadCMS } from "@/modules/providers/payloadcms/payloadcms";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { SaleorProviderFieldsMappingSchema } from "./saleor-provider-fields-mapping.schema";
|
||||||
|
|
||||||
|
const InputSchema = z.object({
|
||||||
|
type: z.literal(PayloadCMS.type),
|
||||||
|
authToken: z.string(),
|
||||||
|
configName: z.string().min(1),
|
||||||
|
collectionName: z.string().min(1),
|
||||||
|
productVariantFieldsMapping: SaleorProviderFieldsMappingSchema,
|
||||||
|
payloadApiUrl: z.string().url(),
|
||||||
|
authenticatedUserSlug: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const FullSchema = InputSchema.extend({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export namespace PayloadCmsProviderConfig {
|
||||||
|
export type InputShape = z.infer<typeof InputSchema>;
|
||||||
|
export type FullShape = z.infer<typeof FullSchema>;
|
||||||
|
|
||||||
|
export const Schema = {
|
||||||
|
Input: InputSchema,
|
||||||
|
Full: FullSchema,
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { ContentfulProviderConfig } from "./contentful-provider.schema";
|
||||||
import { BuilderIoProviderConfig } from "./builder-provider.schema";
|
import { BuilderIoProviderConfig } from "./builder-provider.schema";
|
||||||
import { StrapiProviderConfig } from "./strapi-provider.schema";
|
import { StrapiProviderConfig } from "./strapi-provider.schema";
|
||||||
import { DatocmsProviderConfig } from "./datocms-provider.schema";
|
import { DatocmsProviderConfig } from "./datocms-provider.schema";
|
||||||
|
import { PayloadCmsProviderConfig } from "./payloadcms-provider.schema";
|
||||||
|
|
||||||
export namespace ProvidersConfig {
|
export namespace ProvidersConfig {
|
||||||
const AnyFull = z.union([
|
const AnyFull = z.union([
|
||||||
|
@ -14,6 +15,7 @@ export namespace ProvidersConfig {
|
||||||
DatocmsProviderConfig.Schema.Full,
|
DatocmsProviderConfig.Schema.Full,
|
||||||
StrapiProviderConfig.Schema.Full,
|
StrapiProviderConfig.Schema.Full,
|
||||||
BuilderIoProviderConfig.Schema.Full,
|
BuilderIoProviderConfig.Schema.Full,
|
||||||
|
PayloadCmsProviderConfig.Schema.Full,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const Schema = {
|
export const Schema = {
|
||||||
|
@ -23,6 +25,7 @@ export namespace ProvidersConfig {
|
||||||
DatocmsProviderConfig.Schema.Input,
|
DatocmsProviderConfig.Schema.Input,
|
||||||
StrapiProviderConfig.Schema.Input,
|
StrapiProviderConfig.Schema.Input,
|
||||||
BuilderIoProviderConfig.Schema.Input,
|
BuilderIoProviderConfig.Schema.Input,
|
||||||
|
PayloadCmsProviderConfig.Schema.Input,
|
||||||
]),
|
]),
|
||||||
AnyFullList: z.array(AnyFull),
|
AnyFullList: z.array(AnyFull),
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,7 +104,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
padding={2}
|
padding={2}
|
||||||
>
|
>
|
||||||
<Text variant="caption">Saleor Field</Text>
|
<Text variant="caption">Saleor Field</Text>
|
||||||
<Text variant="caption">Contentful field</Text>
|
<Text variant="caption">Builder.io field</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
// todo extract this table to component
|
// todo extract this table to component
|
||||||
|
@ -190,7 +190,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!props.configId,
|
enabled: !!props.configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|
|
@ -53,7 +53,7 @@ const PureForm = ({
|
||||||
});
|
});
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch content types from Contentful. Please check your credentials."
|
"Could not fetch content types from Contentful. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -76,7 +76,7 @@ const PureForm = ({
|
||||||
});
|
});
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch environments from Contentful. Please check your credentials."
|
"Could not fetch environments from Contentful. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -197,7 +197,7 @@ const PureForm = ({
|
||||||
newTab
|
newTab
|
||||||
size="small"
|
size="small"
|
||||||
href={`https://app.contentful.com/spaces/${getValues(
|
href={`https://app.contentful.com/spaces/${getValues(
|
||||||
"spaceId"
|
"spaceId",
|
||||||
)}/settings/environments`}
|
)}/settings/environments`}
|
||||||
>
|
>
|
||||||
here
|
here
|
||||||
|
@ -247,7 +247,7 @@ const PureForm = ({
|
||||||
|
|
||||||
<Box marginTop={4}>
|
<Box marginTop={4}>
|
||||||
<Text as="p" variant="heading" size="small">
|
<Text as="p" variant="heading" size="small">
|
||||||
Map fields from Saleor to your contentful schema.
|
Map fields from Saleor to your Contentful schema.
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="p" marginTop={2} marginBottom={4}>
|
<Text as="p" marginTop={2} marginBottom={4}>
|
||||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
||||||
|
@ -357,7 +357,7 @@ const EditVariant = ({ configId }: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!configId,
|
enabled: !!configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|
|
@ -43,7 +43,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
|
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch content types from DatoCMS. Please check your credentials."
|
"Could not fetch content types from DatoCMS. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
|
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch content types from DatoCMS. Please check your credentials."
|
"Could not fetch content types from DatoCMS. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -161,7 +161,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
{fieldsData && (
|
{fieldsData && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Text as="p" variant="heading" size="small">
|
<Text as="p" variant="heading" size="small">
|
||||||
Map fields from Saleor to your contentful schema.
|
Map fields from Saleor to your DatoCMS schema.
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="p" marginTop={2} marginBottom={4}>
|
<Text as="p" marginTop={2} marginBottom={4}>
|
||||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
||||||
|
@ -177,7 +177,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
padding={2}
|
padding={2}
|
||||||
>
|
>
|
||||||
<Text variant="caption">Saleor Field</Text>
|
<Text variant="caption">Saleor Field</Text>
|
||||||
<Text variant="caption">Contentful field</Text>
|
<Text variant="caption">DatoCMS field</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
// todo extract this table to component
|
// todo extract this table to component
|
||||||
|
@ -271,7 +271,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!props.configId,
|
enabled: !!props.configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|
BIN
apps/cms-v2/src/modules/providers/payloadcms/logo.png
Normal file
BIN
apps/cms-v2/src/modules/providers/payloadcms/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1,51 @@
|
||||||
|
import { BulkImportProductFragment } from "../../../../generated/graphql";
|
||||||
|
import { BulkSyncProcessor, BulkSyncProcessorHooks } from "../../bulk-sync/bulk-sync-processor";
|
||||||
|
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { PayloadCMSClient } from "./payloadcms-client";
|
||||||
|
|
||||||
|
// todo CORS or proxy
|
||||||
|
export class PayloadCmsBulkSyncProcessor implements BulkSyncProcessor {
|
||||||
|
constructor(private config: PayloadCmsProviderConfig.FullShape) {}
|
||||||
|
|
||||||
|
async uploadProducts(
|
||||||
|
products: BulkImportProductFragment[],
|
||||||
|
hooks: BulkSyncProcessorHooks,
|
||||||
|
): Promise<void> {
|
||||||
|
const client = new PayloadCMSClient();
|
||||||
|
|
||||||
|
products.flatMap(
|
||||||
|
(product) =>
|
||||||
|
product.variants?.map((variant) => {
|
||||||
|
if (hooks.onUploadStart) {
|
||||||
|
hooks.onUploadStart({ variantId: variant.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
.upsertProductVariant({
|
||||||
|
configuration: this.config,
|
||||||
|
variant: {
|
||||||
|
id: variant.id,
|
||||||
|
name: variant.name,
|
||||||
|
channelListings: variant.channelListings,
|
||||||
|
product: {
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
slug: product.slug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((r) => {
|
||||||
|
if (hooks.onUploadSuccess) {
|
||||||
|
hooks.onUploadSuccess({ variantId: variant.id });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (hooks.onUploadError) {
|
||||||
|
hooks.onUploadError({ variantId: variant.id, error: e });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
import { WebhookProductVariantFragment } from "../../../../generated/graphql";
|
||||||
|
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { FieldsMapper } from "../fields-mapper";
|
||||||
|
|
||||||
|
import qs from "qs";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
configuration: PayloadCmsProviderConfig.FullShape;
|
||||||
|
variant: WebhookProductVariantFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client uses REST API with built-in query language
|
||||||
|
* https://payloadcms.com/docs/queries/overview#rest-queries
|
||||||
|
*/
|
||||||
|
export class PayloadCMSClient {
|
||||||
|
private logger = createLogger({ name: "PayloadCMSClient" });
|
||||||
|
|
||||||
|
private mapVariantToPayloadFields({ configuration, variant }: Context) {
|
||||||
|
const fields = FieldsMapper.mapProductVariantToConfigurationFields({
|
||||||
|
variant,
|
||||||
|
configMapping: configuration.productVariantFieldsMapping,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructCollectionUrl(config: PayloadCmsProviderConfig.FullShape) {
|
||||||
|
return `${config.payloadApiUrl}/${config.collectionName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemsBySaleorVariantId(context: Context) {
|
||||||
|
const queryString = qs.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
[context.configuration.productVariantFieldsMapping.variantId]: {
|
||||||
|
equals: context.variant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return fetch(`${this.constructCollectionUrl(context.configuration)}${queryString}`, {
|
||||||
|
headers: this.getHeaders(context),
|
||||||
|
}).then((r) => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProductVariant(context: Context) {
|
||||||
|
const queryString = qs.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
[context.configuration.productVariantFieldsMapping.variantId]: {
|
||||||
|
equals: context.variant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
this.constructCollectionUrl(context.configuration) + queryString,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: this.getHeaders(context),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error("Error while deleting product variant");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeaders(context: Context) {
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://payloadcms.com/docs/authentication/config#api-keys
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
context.configuration.authToken.length > 0 &&
|
||||||
|
context.configuration.authenticatedUserSlug.length > 0
|
||||||
|
) {
|
||||||
|
headers.append(
|
||||||
|
"Authorization",
|
||||||
|
`${context.configuration.authenticatedUserSlug} API-Key ${context.configuration.authToken}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadProductVariant(context: Context) {
|
||||||
|
this.logger.debug("Trying to upload product variant");
|
||||||
|
|
||||||
|
return fetch(this.constructCollectionUrl(context.configuration), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(this.mapVariantToPayloadFields(context)),
|
||||||
|
headers: this.getHeaders(context),
|
||||||
|
})
|
||||||
|
.then((r) => {
|
||||||
|
if (r.status >= 400) {
|
||||||
|
throw new Error(`Error while uploading product variant: ${r.statusText}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.logger.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProductVariant({ configuration, variant }: Context) {
|
||||||
|
this.logger.debug("Trying to update product variant");
|
||||||
|
|
||||||
|
const queryString = qs.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
[configuration.productVariantFieldsMapping.variantId]: {
|
||||||
|
equals: variant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.constructCollectionUrl(configuration) + queryString, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify(this.mapVariantToPayloadFields({ configuration, variant })),
|
||||||
|
headers: this.getHeaders({ configuration, variant }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error("Error while updating product variant");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertProductVariant(context: Context) {
|
||||||
|
this.logger.debug("Trying to upsert product variant");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.uploadProductVariant(context);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.debug("Failed to upload, will try to update");
|
||||||
|
|
||||||
|
await this.updateProductVariant(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
|
import { Input, Select } from "@saleor/react-hook-form-macaw";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||||
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
|
import { ButtonsBox } from "../../ui/buttons-box";
|
||||||
|
import { TextLink } from "@saleor/apps-ui";
|
||||||
|
|
||||||
|
type FormShape = Omit<PayloadCmsProviderConfig.InputShape, "type">;
|
||||||
|
|
||||||
|
type PureFormProps = {
|
||||||
|
defaultValues: FormShape;
|
||||||
|
onSubmit(values: FormShape): void;
|
||||||
|
onDelete?(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* todo react on token change, refresh mutation
|
||||||
|
*/
|
||||||
|
const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
|
const { notifyError } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { control, getValues, setValue, watch, handleSubmit, clearErrors, setError } = useForm({
|
||||||
|
defaultValues: defaultValues,
|
||||||
|
resolver: zodResolver(PayloadCmsProviderConfig.Schema.Input.omit({ type: true })),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="form"
|
||||||
|
display={"grid"}
|
||||||
|
gap={4}
|
||||||
|
onSubmit={handleSubmit((vals) => {
|
||||||
|
onSubmit(vals);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
control={control}
|
||||||
|
name="configName"
|
||||||
|
label="Configuration name"
|
||||||
|
helperText="Meaningful name that will help you understand it later. E.g. 'staging' or 'prod' "
|
||||||
|
/>
|
||||||
|
<Box display={"grid"} gap={4} marginY={4}>
|
||||||
|
<Text variant="heading">Provide connection details</Text>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
control={control}
|
||||||
|
name="payloadApiUrl"
|
||||||
|
type="url"
|
||||||
|
label="API url"
|
||||||
|
helperText="URL where Payload API is available. By default ends with /api"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
backgroundColor="surfaceNeutralHighlight"
|
||||||
|
borderColor="neutralHighlight"
|
||||||
|
borderWidth={1}
|
||||||
|
borderStyle={"solid"}
|
||||||
|
padding={4}
|
||||||
|
borderRadius={4}
|
||||||
|
>
|
||||||
|
<Text variant="heading" as="h1" marginBottom={4}>
|
||||||
|
Authorization
|
||||||
|
</Text>
|
||||||
|
<Text marginBottom={2} as="p">
|
||||||
|
Payload can be configured to have open operations (not recommended) or to require an API
|
||||||
|
key. Key can be generated per user. To authenticate, you need to provide both user slug
|
||||||
|
and the key itself.{" "}
|
||||||
|
</Text>
|
||||||
|
<TextLink
|
||||||
|
marginBottom={2}
|
||||||
|
display="block"
|
||||||
|
newTab
|
||||||
|
href="https://payloadcms.com/docs/authentication/config"
|
||||||
|
>
|
||||||
|
Read more in Payload docs
|
||||||
|
</TextLink>
|
||||||
|
<Text as="p" marginBottom={4}>
|
||||||
|
If your API is open (e.g. for development purposes) leave both fields empty.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
control={control}
|
||||||
|
name="authenticatedUserSlug"
|
||||||
|
label="Authenticated user slug"
|
||||||
|
placeholder="e.g. apps"
|
||||||
|
/>
|
||||||
|
<Input control={control} name="authToken" type="password" label="User API Key" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box display={"grid"} gap={4} marginY={4}>
|
||||||
|
<Text variant="heading">Configure fields mapping</Text>
|
||||||
|
<Input
|
||||||
|
label="Collection Slug"
|
||||||
|
name="collectionName"
|
||||||
|
control={control}
|
||||||
|
helperText="Slug of your collection in Payload, e.g. 'saleorVariants'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<React.Fragment>
|
||||||
|
<Text as="p" variant="heading" size="small">
|
||||||
|
Map fields from Saleor to your Payload schema.
|
||||||
|
</Text>
|
||||||
|
<Text as="p" marginTop={2} marginBottom={4}>
|
||||||
|
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should be
|
||||||
|
type of <Text variant="bodyStrong">JSON</Text>.
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
marginBottom={4}
|
||||||
|
display="grid"
|
||||||
|
__gridTemplateColumns={"50% 50%"}
|
||||||
|
borderBottomWidth={1}
|
||||||
|
borderBottomStyle="solid"
|
||||||
|
borderColor="neutralHighlight"
|
||||||
|
padding={2}
|
||||||
|
>
|
||||||
|
<Text variant="caption">Saleor Field</Text>
|
||||||
|
<Text variant="caption">Payload field</Text>
|
||||||
|
</Box>
|
||||||
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
|
// todo extract this table to component
|
||||||
|
<Box
|
||||||
|
display="grid"
|
||||||
|
__gridTemplateColumns={"50% 50%"}
|
||||||
|
padding={2}
|
||||||
|
key={saleorField}
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Text as="p" variant="bodyStrong">
|
||||||
|
{printSaleorProductFields(saleorField)}
|
||||||
|
</Text>
|
||||||
|
<Text variant="caption">
|
||||||
|
{saleorField === "channels" ? "JSON field" : "Text field"}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
size="small"
|
||||||
|
control={control}
|
||||||
|
name={`productVariantFieldsMapping.${saleorField}`}
|
||||||
|
label="CMS Field"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<ButtonsBox>
|
||||||
|
{onDelete && (
|
||||||
|
<Button onClick={onDelete} variant="tertiary">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button type="submit">Save</Button>
|
||||||
|
</ButtonsBox>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddFormVariant = () => {
|
||||||
|
const { push } = useRouter();
|
||||||
|
const { notifySuccess } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { mutate } = trpcClient.providersConfigs.addOne.useMutation({
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess("Success", "Added new configuration");
|
||||||
|
push("/configuration");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PureForm
|
||||||
|
onSubmit={(values) => {
|
||||||
|
mutate({
|
||||||
|
...values,
|
||||||
|
type: "payloadcms",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultValues={{
|
||||||
|
payloadApiUrl: "",
|
||||||
|
authToken: "",
|
||||||
|
configName: "",
|
||||||
|
collectionName: "",
|
||||||
|
authenticatedUserSlug: "",
|
||||||
|
productVariantFieldsMapping: {
|
||||||
|
channels: "",
|
||||||
|
variantName: "",
|
||||||
|
productId: "",
|
||||||
|
productName: "",
|
||||||
|
productSlug: "",
|
||||||
|
variantId: "",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditFormVariant = (props: { configId: string }) => {
|
||||||
|
const { push } = useRouter();
|
||||||
|
const { notifySuccess } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { data } = trpcClient.providersConfigs.getOne.useQuery(
|
||||||
|
{
|
||||||
|
id: props.configId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!props.configId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess("Success", "Updated configuration");
|
||||||
|
push("/configuration");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: deleteProvider } = trpcClient.providersConfigs.deleteOne.useMutation({
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess("Success", "Removed configuration");
|
||||||
|
push("/configuration");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type !== "payloadcms") {
|
||||||
|
throw new Error("Trying to fill Payload CMS form with non Payload CMS data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PureForm
|
||||||
|
onDelete={() => {
|
||||||
|
deleteProvider({
|
||||||
|
id: props.configId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
mutate({
|
||||||
|
...values,
|
||||||
|
type: "payloadcms",
|
||||||
|
id: props.configId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultValues={data}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PayloadCMSConfigForm = {
|
||||||
|
PureVariant: PureForm,
|
||||||
|
AddVariant: AddFormVariant,
|
||||||
|
EditVariant: EditFormVariant,
|
||||||
|
};
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
WebhookProductFragment,
|
||||||
|
WebhookProductVariantFragment,
|
||||||
|
} from "../../../../generated/graphql";
|
||||||
|
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
import { ProductWebhooksProcessor } from "../../webhooks-operations/product-webhooks-processor";
|
||||||
|
import { PayloadCMSClient } from "./payloadcms-client";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* todo error handling
|
||||||
|
*/
|
||||||
|
export class PayloadCmsWebhooksProcessor implements ProductWebhooksProcessor {
|
||||||
|
private client = new PayloadCMSClient();
|
||||||
|
|
||||||
|
private logger = createLogger({ name: "PayloadCmsWebhooksProcessor" });
|
||||||
|
|
||||||
|
constructor(private providerConfig: PayloadCmsProviderConfig.FullShape) {}
|
||||||
|
|
||||||
|
async onProductVariantUpdated(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductVariantUpdated called");
|
||||||
|
|
||||||
|
await this.client.upsertProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: productVariant,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onProductVariantCreated(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductVariantCreated called");
|
||||||
|
|
||||||
|
await this.client.uploadProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: productVariant,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async onProductVariantDeleted(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductVariantDeleted called");
|
||||||
|
|
||||||
|
await this.client.deleteProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: productVariant,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onProductUpdated(product: WebhookProductFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductUpdated called");
|
||||||
|
|
||||||
|
const client = new PayloadCMSClient();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
(product.variants ?? []).map((variant) => {
|
||||||
|
return client.upsertProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: {
|
||||||
|
id: variant.id,
|
||||||
|
name: variant.name,
|
||||||
|
product: {
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
slug: product.slug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
29
apps/cms-v2/src/modules/providers/payloadcms/payloadcms.tsx
Normal file
29
apps/cms-v2/src/modules/providers/payloadcms/payloadcms.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
|
import { CMSProviderMeta } from "../cms-provider-meta";
|
||||||
|
import logo from "./logo.png";
|
||||||
|
import { TextLink } from "@saleor/apps-ui";
|
||||||
|
|
||||||
|
export const PayloadCMS = {
|
||||||
|
formSideInfo: (
|
||||||
|
<Box>
|
||||||
|
<Text as="p" marginBottom={2}>
|
||||||
|
Configure the Payload CMS integration by providing required information.
|
||||||
|
</Text>
|
||||||
|
<Text as="p" marginBottom={2}>
|
||||||
|
Fields are not validated - ensure you enter correct values.
|
||||||
|
</Text>
|
||||||
|
<Text as="p" marginBottom={2}>
|
||||||
|
Consult{" "}
|
||||||
|
<TextLink newTab href="https://docs.saleor.io/docs/3.x/developer/app-store/apps/cms">
|
||||||
|
docs
|
||||||
|
</TextLink>{" "}
|
||||||
|
for more information how to set up Payload CMS.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
type: "payloadcms" as const,
|
||||||
|
logoUrl: logo.src,
|
||||||
|
displayName: "Payload",
|
||||||
|
description: "Open source, typescript first headless CMS. GraphQL included.",
|
||||||
|
} satisfies CMSProviderMeta;
|
|
@ -1,12 +1,24 @@
|
||||||
import { BuilderIo } from "./builder.io/builder-io";
|
import { BuilderIo } from "./builder.io/builder-io";
|
||||||
import { Contentful } from "./contentful/contentful";
|
import { Contentful } from "./contentful/contentful";
|
||||||
import { Datocms } from "./datocms/datocms";
|
import { Datocms } from "./datocms/datocms";
|
||||||
|
import { PayloadCMS } from "./payloadcms/payloadcms";
|
||||||
import { Strapi } from "./strapi/strapi";
|
import { Strapi } from "./strapi/strapi";
|
||||||
|
|
||||||
export type CMS = typeof Contentful | typeof Datocms | typeof Strapi | typeof BuilderIo;
|
export type CMS =
|
||||||
|
| typeof Contentful
|
||||||
|
| typeof Datocms
|
||||||
|
| typeof Strapi
|
||||||
|
| typeof BuilderIo
|
||||||
|
| typeof PayloadCMS;
|
||||||
|
|
||||||
export type CMSType = CMS["type"];
|
export type CMSType = CMS["type"];
|
||||||
|
|
||||||
export const cmsTypes = [Contentful.type, Datocms.type, Strapi.type, BuilderIo.type] as const;
|
export const cmsTypes = [
|
||||||
|
Contentful.type,
|
||||||
|
Datocms.type,
|
||||||
|
Strapi.type,
|
||||||
|
BuilderIo.type,
|
||||||
|
PayloadCMS.type,
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const CMSProviders = [Contentful, Datocms, Strapi, BuilderIo] as const;
|
export const CMSProviders = [Contentful, Datocms, Strapi, BuilderIo, PayloadCMS] as const;
|
||||||
|
|
|
@ -22,6 +22,10 @@ import { DatocmsProviderConfig } from "../configuration/schemas/datocms-provider
|
||||||
import { BuilderIo } from "./builder.io/builder-io";
|
import { BuilderIo } from "./builder.io/builder-io";
|
||||||
import { BuilderIoWebhooksProcessor } from "./builder.io/builder-io-webhooks-processor";
|
import { BuilderIoWebhooksProcessor } from "./builder.io/builder-io-webhooks-processor";
|
||||||
import { BuilderIoBulkSyncProcessor } from "./builder.io/builder-io-bulk-sync-processor";
|
import { BuilderIoBulkSyncProcessor } from "./builder.io/builder-io-bulk-sync-processor";
|
||||||
|
import { PayloadCmsBulkSyncProcessor } from "./payloadcms/payloadcms-bulk-sync-processor";
|
||||||
|
import { PayloadCmsProviderConfig } from "../configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { PayloadCMS } from "./payloadcms/payloadcms";
|
||||||
|
import { PayloadCmsWebhooksProcessor } from "./payloadcms/payloadcms-webhooks-processor";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Almost-single source of new providers. Every time app will need to resolve a provider, it will use on of these factories.
|
* Almost-single source of new providers. Every time app will need to resolve a provider, it will use on of these factories.
|
||||||
|
@ -39,9 +43,9 @@ export const ProvidersResolver = {
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return new BuilderIoBulkSyncProcessor(config);
|
return new BuilderIoBulkSyncProcessor(config);
|
||||||
}
|
}
|
||||||
|
case "payloadcms": {
|
||||||
default:
|
return new PayloadCmsBulkSyncProcessor(config);
|
||||||
throw new Error(`Unknown provider`);
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProviderInputSchema(type: CMSType) {
|
getProviderInputSchema(type: CMSType) {
|
||||||
|
@ -54,9 +58,8 @@ export const ProvidersResolver = {
|
||||||
return StrapiProviderConfig.Schema.Input;
|
return StrapiProviderConfig.Schema.Input;
|
||||||
case "builder.io":
|
case "builder.io":
|
||||||
return BuilderIoProviderConfig.Schema.Input;
|
return BuilderIoProviderConfig.Schema.Input;
|
||||||
default: {
|
case "payloadcms":
|
||||||
throw new Error("Failed to build input schema");
|
return PayloadCmsProviderConfig.Schema.Input;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProviderSchema(type: CMSType) {
|
getProviderSchema(type: CMSType) {
|
||||||
|
@ -69,12 +72,11 @@ export const ProvidersResolver = {
|
||||||
return StrapiProviderConfig.Schema.Full;
|
return StrapiProviderConfig.Schema.Full;
|
||||||
case "builder.io":
|
case "builder.io":
|
||||||
return BuilderIoProviderConfig.Schema.Full;
|
return BuilderIoProviderConfig.Schema.Full;
|
||||||
default: {
|
case "payloadcms":
|
||||||
throw new Error("Failed to build provdier schema");
|
return PayloadCmsProviderConfig.Schema.Full;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createProviderMeta(type: CMSType | string): CMS {
|
createProviderMeta(type: CMSType): CMS {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "contentful": {
|
case "contentful": {
|
||||||
return Contentful;
|
return Contentful;
|
||||||
|
@ -88,8 +90,8 @@ export const ProvidersResolver = {
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return BuilderIo;
|
return BuilderIo;
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Unknown provider");
|
return PayloadCMS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -107,13 +109,13 @@ export const ProvidersResolver = {
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return new BuilderIoWebhooksProcessor(config);
|
return new BuilderIoWebhooksProcessor(config);
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Failed to build webhook processor.");
|
return new PayloadCmsWebhooksProcessor(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getEditProviderFormComponent: (
|
getEditProviderFormComponent: (
|
||||||
type: CMSType
|
type: CMSType,
|
||||||
): ComponentType<{
|
): ComponentType<{
|
||||||
configId: string;
|
configId: string;
|
||||||
}> => {
|
}> => {
|
||||||
|
@ -121,33 +123,37 @@ export const ProvidersResolver = {
|
||||||
case "contentful": {
|
case "contentful": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./contentful/contentful-config-form").then(
|
import("./contentful/contentful-config-form").then(
|
||||||
(module) => module.ContentfulConfigForm.EditVariant
|
(module) => module.ContentfulConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "datocms": {
|
case "datocms": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./datocms/datocms-config-form").then(
|
import("./datocms/datocms-config-form").then(
|
||||||
(module) => module.DatoCMSConfigForm.EditVariant
|
(module) => module.DatoCMSConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "strapi": {
|
case "strapi": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./strapi/strapi-config-form").then(
|
import("./strapi/strapi-config-form").then(
|
||||||
(module) => module.StrapiConfigForm.EditVariant
|
(module) => module.StrapiConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./builder.io/builder-io-config-form").then(
|
import("./builder.io/builder-io-config-form").then(
|
||||||
(module) => module.BuilderIoConfigForm.EditVariant
|
(module) => module.BuilderIoConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Provider form not registered");
|
return dynamic(() =>
|
||||||
|
import("./payloadcms/payloadcms-config-form").then(
|
||||||
|
(module) => module.PayloadCMSConfigForm.EditVariant,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -156,31 +162,37 @@ export const ProvidersResolver = {
|
||||||
case "contentful": {
|
case "contentful": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./contentful/contentful-config-form").then(
|
import("./contentful/contentful-config-form").then(
|
||||||
(module) => module.ContentfulConfigForm.AddVariant
|
(module) => module.ContentfulConfigForm.AddVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "datocms": {
|
case "datocms": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./datocms/datocms-config-form").then(
|
import("./datocms/datocms-config-form").then(
|
||||||
(module) => module.DatoCMSConfigForm.AddVariant
|
(module) => module.DatoCMSConfigForm.AddVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "strapi": {
|
case "strapi": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./strapi/strapi-config-form").then((module) => module.StrapiConfigForm.AddVariant)
|
import("./strapi/strapi-config-form").then(
|
||||||
|
(module) => module.StrapiConfigForm.AddVariant,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./builder.io/builder-io-config-form").then(
|
import("./builder.io/builder-io-config-form").then(
|
||||||
(module) => module.BuilderIoConfigForm.AddVariant
|
(module) => module.BuilderIoConfigForm.AddVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Provider form not registered");
|
return dynamic(() =>
|
||||||
|
import("./payloadcms/payloadcms-config-form").then(
|
||||||
|
(module) => module.PayloadCMSConfigForm.AddVariant,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -86,7 +86,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
padding={2}
|
padding={2}
|
||||||
>
|
>
|
||||||
<Text variant="caption">Saleor Field</Text>
|
<Text variant="caption">Saleor Field</Text>
|
||||||
<Text variant="caption">Contentful field</Text>
|
<Text variant="caption">Strapi field</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
// todo extract this table to component
|
// todo extract this table to component
|
||||||
|
@ -172,7 +172,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!props.configId,
|
enabled: !!props.configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ dialog {
|
||||||
|
|
||||||
.dialog-overlay {
|
.dialog-overlay {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: rgba(var(--mu-colors-background-plain), 0.8);
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
||||||
import { forwardRef } from "react";
|
|
||||||
|
|
||||||
export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
||||||
return (
|
return (
|
||||||
|
@ -12,6 +11,8 @@ export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
||||||
as="dialog"
|
as="dialog"
|
||||||
__maxWidth="400px"
|
__maxWidth="400px"
|
||||||
boxShadow={"modal"}
|
boxShadow={"modal"}
|
||||||
|
backgroundColor="surfaceNeutralPlain"
|
||||||
|
color="textNeutralDefault"
|
||||||
open
|
open
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { ContentfulConfigForm } from "@/modules/providers/contentful/contentful-config-form";
|
|
||||||
import { DatoCMSConfigForm } from "@/modules/providers/datocms/datocms-config-form";
|
|
||||||
import { CMSType } from "@/modules/providers/providers-registry";
|
import { CMSType } from "@/modules/providers/providers-registry";
|
||||||
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
||||||
|
|
||||||
import { StrapiConfigForm } from "@/modules/providers/strapi/strapi-config-form";
|
|
||||||
import { AppHeader } from "@/modules/ui/app-header";
|
import { AppHeader } from "@/modules/ui/app-header";
|
||||||
import { AppSection } from "@/modules/ui/app-section";
|
import { AppSection } from "@/modules/ui/app-section";
|
||||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||||
|
@ -16,7 +13,7 @@ const AddProviderPage: NextPage = () => {
|
||||||
const { query } = useRouter();
|
const { query } = useRouter();
|
||||||
|
|
||||||
const provider = useMemo(() => {
|
const provider = useMemo(() => {
|
||||||
return query.type ? ProvidersResolver.createProviderMeta(query.type as string) : null;
|
return query.type ? ProvidersResolver.createProviderMeta(query.type as CMSType) : null;
|
||||||
}, [query.type]);
|
}, [query.type]);
|
||||||
|
|
||||||
if (!provider) return null;
|
if (!provider) return null;
|
||||||
|
|
|
@ -2,11 +2,11 @@ import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||||
import { Client } from "urql";
|
import { Client } from "urql";
|
||||||
|
|
||||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
|
||||||
|
|
||||||
export const createSettingsManager = (
|
export const createSettingsManager = (
|
||||||
client: Pick<Client, "query" | "mutation">,
|
client: Pick<Client, "query" | "mutation">,
|
||||||
appId: string,
|
appId: string,
|
||||||
): SettingsManager => {
|
): SettingsManager => {
|
||||||
|
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||||
|
|
||||||
return metadataManagerFactory.create(client, appId);
|
return metadataManagerFactory.create(client, appId);
|
||||||
};
|
};
|
||||||
|
|
|
@ -106,6 +106,9 @@ importers:
|
||||||
pino-pretty:
|
pino-pretty:
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
|
qs:
|
||||||
|
specifier: 6.11.2
|
||||||
|
version: 6.11.2
|
||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
|
@ -164,6 +167,9 @@ importers:
|
||||||
'@testing-library/react-hooks':
|
'@testing-library/react-hooks':
|
||||||
specifier: ^8.0.1
|
specifier: ^8.0.1
|
||||||
version: 8.0.1(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
|
version: 8.0.1(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@types/qs':
|
||||||
|
specifier: ^6.9.7
|
||||||
|
version: 6.9.7
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: 18.2.5
|
specifier: 18.2.5
|
||||||
version: 18.2.5
|
version: 18.2.5
|
||||||
|
@ -5799,7 +5805,7 @@ packages:
|
||||||
/@changesets/apply-release-plan@6.1.4:
|
/@changesets/apply-release-plan@6.1.4:
|
||||||
resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==}
|
resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/config': 2.3.1
|
'@changesets/config': 2.3.1
|
||||||
'@changesets/get-version-range-type': 0.3.2
|
'@changesets/get-version-range-type': 0.3.2
|
||||||
'@changesets/git': 2.0.0
|
'@changesets/git': 2.0.0
|
||||||
|
@ -5816,7 +5822,7 @@ packages:
|
||||||
/@changesets/assemble-release-plan@5.2.4:
|
/@changesets/assemble-release-plan@5.2.4:
|
||||||
resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==}
|
resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/errors': 0.1.4
|
'@changesets/errors': 0.1.4
|
||||||
'@changesets/get-dependents-graph': 1.3.6
|
'@changesets/get-dependents-graph': 1.3.6
|
||||||
'@changesets/types': 5.2.1
|
'@changesets/types': 5.2.1
|
||||||
|
@ -5894,7 +5900,7 @@ packages:
|
||||||
/@changesets/get-release-plan@3.0.17:
|
/@changesets/get-release-plan@3.0.17:
|
||||||
resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==}
|
resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/assemble-release-plan': 5.2.4
|
'@changesets/assemble-release-plan': 5.2.4
|
||||||
'@changesets/config': 2.3.1
|
'@changesets/config': 2.3.1
|
||||||
'@changesets/pre': 1.0.14
|
'@changesets/pre': 1.0.14
|
||||||
|
@ -5908,7 +5914,7 @@ packages:
|
||||||
/@changesets/git@2.0.0:
|
/@changesets/git@2.0.0:
|
||||||
resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==}
|
resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/errors': 0.1.4
|
'@changesets/errors': 0.1.4
|
||||||
'@changesets/types': 5.2.1
|
'@changesets/types': 5.2.1
|
||||||
'@manypkg/get-packages': 1.1.3
|
'@manypkg/get-packages': 1.1.3
|
||||||
|
@ -5930,7 +5936,7 @@ packages:
|
||||||
/@changesets/pre@1.0.14:
|
/@changesets/pre@1.0.14:
|
||||||
resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==}
|
resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/errors': 0.1.4
|
'@changesets/errors': 0.1.4
|
||||||
'@changesets/types': 5.2.1
|
'@changesets/types': 5.2.1
|
||||||
'@manypkg/get-packages': 1.1.3
|
'@manypkg/get-packages': 1.1.3
|
||||||
|
@ -5939,7 +5945,7 @@ packages:
|
||||||
/@changesets/read@0.5.9:
|
/@changesets/read@0.5.9:
|
||||||
resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==}
|
resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/git': 2.0.0
|
'@changesets/git': 2.0.0
|
||||||
'@changesets/logger': 0.0.5
|
'@changesets/logger': 0.0.5
|
||||||
'@changesets/parse': 0.3.16
|
'@changesets/parse': 0.3.16
|
||||||
|
@ -5957,7 +5963,7 @@ packages:
|
||||||
/@changesets/write@0.2.3:
|
/@changesets/write@0.2.3:
|
||||||
resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==}
|
resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/types': 5.2.1
|
'@changesets/types': 5.2.1
|
||||||
fs-extra: 7.0.1
|
fs-extra: 7.0.1
|
||||||
human-id: 1.0.2
|
human-id: 1.0.2
|
||||||
|
@ -7698,7 +7704,7 @@ packages:
|
||||||
/@manypkg/get-packages@1.1.3:
|
/@manypkg/get-packages@1.1.3:
|
||||||
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
|
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.22.6
|
'@babel/runtime': 7.22.10
|
||||||
'@changesets/types': 4.1.0
|
'@changesets/types': 4.1.0
|
||||||
'@manypkg/find-root': 1.1.0
|
'@manypkg/find-root': 1.1.0
|
||||||
fs-extra: 8.1.0
|
fs-extra: 8.1.0
|
||||||
|
|
Loading…
Reference in a new issue