Apply recent ESLint rules on the codebase (#404)

* Add lint:fix script

* Reformat CRM app with eslint fix

* Apply eslint fix on data importer codebase

* Apply eslint fix on Invoices codebase

* Apply eslint fix on Klaviyo codebase

* Apply eslint fix on products-feed codebase

* Apply eslint fix on monitoring codebase

* Apply eslint fix on Search codebase

* Apply eslint fix on Slack codebase

* cleanup
This commit is contained in:
Lukasz Ostrowski 2023-04-18 15:10:00 +02:00 committed by GitHub
parent 57f6d41bc4
commit 2c0df91351
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 612 additions and 364 deletions

View file

@ -0,0 +1,16 @@
---
"saleor-app-emails-and-messages": patch
"saleor-app-data-importer": patch
"saleor-app-products-feed": patch
"saleor-app-monitoring": patch
"@saleor/apps-shared": patch
"saleor-app-invoices": patch
"saleor-app-klaviyo": patch
"saleor-app-search": patch
"saleor-app-slack": patch
"saleor-app-taxes": patch
"saleor-app-cms": patch
"saleor-app-crm": patch
---
Added lint:fix script, so `eslint --fix` can be run deliberately

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest" "test": "vitest"
@ -63,9 +64,5 @@
"prettier": "^2.7.1", "prettier": "^2.7.1",
"typescript": "4.9", "typescript": "4.9",
"vitest": "^0.30.1" "vitest": "^0.30.1"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest" "test": "vitest"
@ -65,9 +66,5 @@
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"prettier": "^2.8.2", "prettier": "^2.8.2",
"typescript": "4.9.4" "typescript": "4.9.4"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -90,9 +90,11 @@ export async function mutateMetadata(
export const createSettingsManager = ( export const createSettingsManager = (
client: Pick<Client, "query" | "mutation"> client: Pick<Client, "query" | "mutation">
): SettingsManager => { ): SettingsManager => {
// EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. /*
// We recommend it for production, because all values are encrypted. * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
// If your use case require plain text values, you can use MetadataManager. * We recommend it for production, because all values are encrypted.
* If your use case require plain text values, you can use MetadataManager.
*/
return new EncryptedMetadataManager({ return new EncryptedMetadataManager({
// Secret key should be randomly created for production and set as environment variable // Secret key should be randomly created for production and set as environment variable
encryptionKey: process.env.SECRET_KEY!, encryptionKey: process.env.SECRET_KEY!,

View file

@ -4,6 +4,7 @@ import { createLogger } from "../../../../lib/logger";
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => { export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
const { host, "x-forwarded-proto": protocol = "http" } = headers; const { host, "x-forwarded-proto": protocol = "http" } = headers;
return `${protocol}://${host}`; return `${protocol}://${host}`;
}; };

View file

@ -6,6 +6,7 @@ import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@sale
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => { export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
const { host, "x-forwarded-proto": protocol = "http" } = headers; const { host, "x-forwarded-proto": protocol = "http" } = headers;
return `${protocol}://${host}`; return `${protocol}://${host}`;
}; };
@ -38,6 +39,7 @@ const handler: NextApiHandler = async (req, res) => {
}); });
const redirectUri = `${getBaseUrl(req.headers)}/api/auth/mailchimp/callback`; const redirectUri = `${getBaseUrl(req.headers)}/api/auth/mailchimp/callback`;
logger.debug({ redirectUri }, "Resolved redirect uri"); logger.debug({ redirectUri }, "Resolved redirect uri");
const qs = new URLSearchParams({ const qs = new URLSearchParams({

View file

@ -1,7 +1,9 @@
// This file sets a custom webpack configuration to use your Next.js app /*
// with Sentry. * This file sets a custom webpack configuration to use your Next.js app
// https://nextjs.org/docs/api-reference/next.config.js/introduction * with Sentry.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ * https://nextjs.org/docs/api-reference/next.config.js/introduction
* https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
*/
const { withSentryConfig } = require("@sentry/nextjs"); const { withSentryConfig } = require("@sentry/nextjs");
const isSentryPropertiesInEnvironment = const isSentryPropertiesInEnvironment =

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest", "test": "vitest",
@ -61,9 +62,5 @@
"eslint": "^8.33.0", "eslint": "^8.33.0",
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"typescript": "4.9.5" "typescript": "4.9.5"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the browser. /*
// The config you add here will be used whenever a page is visited. * This file configures the initialization of Sentry on the browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever a page is visited.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever middleware or an Edge route handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever middleware or an Edge route handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever the server handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever the server handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -86,8 +86,10 @@ const generateAddressColumns = (labelNamespace: string, keyNamespace: string): C
// TODO - enable address columns when mapped // TODO - enable address columns when mapped
const allColumns: ColumnAPI[] = [ const allColumns: ColumnAPI[] = [
...customerColumns, ...customerColumns,
// ...generateAddressColumns("Default Billing Address", "defaultBillingAddress"), /*
// ...generateAddressColumns("Default Shipping Address", "defaultShippingAddress"), * ...generateAddressColumns("Default Billing Address", "defaultBillingAddress"),
* ...generateAddressColumns("Default Shipping Address", "defaultShippingAddress"),
*/
]; ];
export const getCustomersModelColumns = () => allColumns; export const getCustomersModelColumns = () => allColumns;
@ -116,8 +118,10 @@ export const getResultModelSchema = () =>
email: z.string(), email: z.string(),
note: z.string().nullish(), note: z.string().nullish(),
externalReference: z.string().nullish(), externalReference: z.string().nullish(),
// defaultBillingAddress: zodAddressSchema, /*
// defaultShippingAddress: zodAddressSchema, * defaultBillingAddress: zodAddressSchema,
* defaultShippingAddress: zodAddressSchema,
*/
}), }),
}); });

View file

@ -57,6 +57,7 @@ function NextApp({ Component, pageProps }: AppProps) {
*/ */
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -20,16 +20,20 @@ import * as Sentry from "@sentry/nextjs";
import NextErrorComponent from "next/error"; import NextErrorComponent from "next/error";
const CustomErrorComponent = (props) => { const CustomErrorComponent = (props) => {
// If you're using a Nextjs version prior to 12.2.1, uncomment this to /*
// compensate for https://github.com/vercel/next.js/issues/8592 * If you're using a Nextjs version prior to 12.2.1, uncomment this to
// Sentry.captureUnderscoreErrorException(props); * compensate for https://github.com/vercel/next.js/issues/8592
* Sentry.captureUnderscoreErrorException(props);
*/
return <NextErrorComponent statusCode={props.statusCode} />; return <NextErrorComponent statusCode={props.statusCode} />;
}; };
CustomErrorComponent.getInitialProps = async (contextData) => { CustomErrorComponent.getInitialProps = async (contextData) => {
// In case this is running in a serverless function, await this in order to give Sentry /*
// time to send the error before the lambda exits * In case this is running in a serverless function, await this in order to give Sentry
* time to send the error before the lambda exits
*/
await Sentry.captureUnderscoreErrorException(contextData); await Sentry.captureUnderscoreErrorException(contextData);
// This will contain the status code of the response // This will contain the status code of the response

View file

@ -16,7 +16,11 @@
"REST_APL_TOKEN", "REST_APL_TOKEN",
"NEXT_PUBLIC_VERCEL_ENV", "NEXT_PUBLIC_VERCEL_ENV",
"VERCEL_URL", "VERCEL_URL",
"PORT" "PORT",
"SENTRY_AUTH_TOKEN",
"SENTRY_PROJECT",
"SENTRY_ORG",
"SENTRY_DSN"
] ]
} }
} }

View file

@ -6,7 +6,8 @@
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "pnpm generate && prettier --loglevel warn --write . && eslint --fix .", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest" "test": "vitest"
@ -78,9 +79,5 @@
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"prettier": "^2.8.2", "prettier": "^2.8.2",
"typescript": "4.9.4" "typescript": "4.9.4"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -1,7 +1,9 @@
// This file sets a custom webpack configuration to use your Next.js app /*
// with Sentry. * This file sets a custom webpack configuration to use your Next.js app
// https://nextjs.org/docs/api-reference/next.config.js/introduction * with Sentry.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * https://nextjs.org/docs/api-reference/next.config.js/introduction
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
const { withSentryConfig } = require("@sentry/nextjs"); const { withSentryConfig } = require("@sentry/nextjs");
@ -13,12 +15,14 @@ const isSentryPropertiesInEnvironment =
*/ */
const moduleExports = { const moduleExports = {
sentry: { sentry: {
// Use `hidden-source-map` rather than `source-map` as the Webpack `devtool` /*
// for client-side builds. (This will be the default starting in * Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
// `@sentry/nextjs` version 8.0.0.) See * for client-side builds. (This will be the default starting in
// https://webpack.js.org/configuration/devtool/ and * `@sentry/nextjs` version 8.0.0.) See
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map * https://webpack.js.org/configuration/devtool/ and
// for more information. * https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
* for more information.
*/
hideSourceMaps: true, hideSourceMaps: true,
disableServerWebpackPlugin: !isSentryPropertiesInEnvironment, disableServerWebpackPlugin: !isSentryPropertiesInEnvironment,
disableClientWebpackPlugin: !isSentryPropertiesInEnvironment, disableClientWebpackPlugin: !isSentryPropertiesInEnvironment,
@ -31,17 +35,23 @@ const moduleExports = {
}; };
const sentryWebpackPluginOptions = { const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that /*
// the following options are set automatically, and overriding them is not * Additional config options for the Sentry Webpack plugin. Keep in mind that
// recommended: * the following options are set automatically, and overriding them is not
// release, url, org, project, authToken, configFile, stripPrefix, * recommended:
// urlPrefix, include, ignore * release, url, org, project, authToken, configFile, stripPrefix,
* urlPrefix, include, ignore
*/
silent: true, // Suppresses all logs silent: true, // Suppresses all logs
// For all available options, see: /*
// https://github.com/getsentry/sentry-webpack-plugin#options. * For all available options, see:
* https://github.com/getsentry/sentry-webpack-plugin#options.
*/
}; };
// Make sure adding Sentry options is the last code to run before exporting, to /*
// ensure that your source maps include changes from all other Webpack plugins * Make sure adding Sentry options is the last code to run before exporting, to
* ensure that your source maps include changes from all other Webpack plugins
*/
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions); module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest", "test": "vitest",
@ -70,10 +71,6 @@
"typescript": "4.9.5", "typescript": "4.9.5",
"vite": "^4.2.1", "vite": "^4.2.1",
"vitest": "^0.30.1", "vitest": "^0.30.1",
"@types/semver": "^7.3.13" "@types/semver": "^7.3.13"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the browser. /*
// The config you add here will be used whenever a page is visited. * This file configures the initialization of Sentry on the browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever a page is visited.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN;
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever middleware or an Edge route handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever middleware or an Edge route handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever the server handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever the server handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -81,9 +81,11 @@ export async function mutateMetadata(client: Client, metadata: MetadataEntry[])
} }
export const createSettingsManager = (client: Client) => { export const createSettingsManager = (client: Client) => {
// EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. /*
// We recommend it for production, because all values are encrypted. * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
// If your use case require plain text values, you can use MetadataManager. * We recommend it for production, because all values are encrypted.
* If your use case require plain text values, you can use MetadataManager.
*/
return new EncryptedMetadataManager({ return new EncryptedMetadataManager({
// Secret key should be randomly created for production and set as environment variable // Secret key should be randomly created for production and set as environment variable
encryptionKey: process.env.SECRET_KEY!, encryptionKey: process.env.SECRET_KEY!,

View file

@ -19,13 +19,15 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
const microinvoiceInstance = new Microinvoice({ const microinvoiceInstance = new Microinvoice({
style: { style: {
// header: { /*
// image: { * header: {
// path: "./examples/logo.png", * image: {
// width: 50, * path: "./examples/logo.png",
// height: 19, * width: 50,
// }, * height: 19,
// }, * },
* },
*/
}, },
data: { data: {
invoice: { invoice: {
@ -60,10 +62,12 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
order.billingAddress?.country.country, order.billingAddress?.country.country,
], ],
}, },
// { /*
// label: "Tax Identifier", * {
// value: "todo", * label: "Tax Identifier",
// }, * value: "todo",
* },
*/
], ],
seller: [ seller: [
@ -79,23 +83,27 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
companyAddressData.countryArea, companyAddressData.countryArea,
], ],
}, },
// { /*
// label: "Tax Identifier", * {
// value: "todo", * label: "Tax Identifier",
// }, * value: "todo",
* },
*/
], ],
legal: [ legal: [
// { /*
// value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit", * {
// weight: "bold", * value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
// color: "primary", * weight: "bold",
// }, * color: "primary",
// { * },
// value: "sed do eiusmod tempor incididunt ut labore et dolore magna.", * {
// weight: "bold", * value: "sed do eiusmod tempor incididunt ut labore et dolore magna.",
// color: "secondary", * weight: "bold",
// }, * color: "secondary",
* },
*/
], ],
details: { details: {

View file

@ -22,6 +22,7 @@ function NextApp({ Component, pageProps }: AppProps) {
*/ */
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -20,16 +20,20 @@ import * as Sentry from '@sentry/nextjs';
import NextErrorComponent from 'next/error'; import NextErrorComponent from 'next/error';
const CustomErrorComponent = props => { const CustomErrorComponent = props => {
// If you're using a Nextjs version prior to 12.2.1, uncomment this to /*
// compensate for https://github.com/vercel/next.js/issues/8592 * If you're using a Nextjs version prior to 12.2.1, uncomment this to
// Sentry.captureUnderscoreErrorException(props); * compensate for https://github.com/vercel/next.js/issues/8592
* Sentry.captureUnderscoreErrorException(props);
*/
return <NextErrorComponent statusCode={props.statusCode} />; return <NextErrorComponent statusCode={props.statusCode} />;
}; };
CustomErrorComponent.getInitialProps = async contextData => { CustomErrorComponent.getInitialProps = async contextData => {
// In case this is running in a serverless function, await this in order to give Sentry /*
// time to send the error before the lambda exits * In case this is running in a serverless function, await this in order to give Sentry
* time to send the error before the lambda exits
*/
await Sentry.captureUnderscoreErrorException(contextData); await Sentry.captureUnderscoreErrorException(contextData);
// This will contain the status code of the response // This will contain the status code of the response

View file

@ -174,6 +174,7 @@ export const handler: NextWebhookApiHandler<InvoiceRequestedPayloadFragment> = a
); );
const hashedInvoiceName = hashInvoiceFilename(invoiceName, orderId); const hashedInvoiceName = hashInvoiceFilename(invoiceName, orderId);
logger.debug({ hashedInvoiceName }); logger.debug({ hashedInvoiceName });
const hashedInvoiceFileName = `${hashedInvoiceName}.pdf`; const hashedInvoiceFileName = `${hashedInvoiceName}.pdf`;

View file

@ -14,7 +14,11 @@
"ALLOWED_DOMAIN_PATTERN", "ALLOWED_DOMAIN_PATTERN",
"NEXT_PUBLIC_VERCEL_ENV", "NEXT_PUBLIC_VERCEL_ENV",
"REST_APL_ENDPOINT", "REST_APL_ENDPOINT",
"REST_APL_TOKEN" "REST_APL_TOKEN",
"SENTRY_PROJECT",
"SENTRY_DSN",
"SENTRY_ORG",
"SENTRY_AUTH_TOKEN"
] ]
} }
} }

View file

@ -28,17 +28,23 @@ const nextConfig = {
}; };
const sentryWebpackPluginOptions = { const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that /*
// the following options are set automatically, and overriding them is not * Additional config options for the Sentry Webpack plugin. Keep in mind that
// recommended: * the following options are set automatically, and overriding them is not
// release, url, org, project, authToken, configFile, stripPrefix, * recommended:
// urlPrefix, include, ignore * release, url, org, project, authToken, configFile, stripPrefix,
* urlPrefix, include, ignore
*/
silent: true, // Suppresses all logs silent: true, // Suppresses all logs
// For all available options, see: /*
// https://github.com/getsentry/sentry-webpack-plugin#options. * For all available options, see:
* https://github.com/getsentry/sentry-webpack-plugin#options.
*/
}; };
// Make sure adding Sentry options is the last code to run before exporting, to /*
// ensure that your source maps include changes from all other Webpack plugins * Make sure adding Sentry options is the last code to run before exporting, to
* ensure that your source maps include changes from all other Webpack plugins
*/
module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions); module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions);

View file

@ -7,7 +7,8 @@
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "pnpm generate && prettier --loglevel warn --write . && eslint --fix .", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen" "generate": "graphql-codegen"
}, },

View file

@ -1,6 +1,8 @@
// This file configures the initialization of Sentry on the browser. /*
// The config you add here will be used whenever a page is visited. * This file configures the initialization of Sentry on the browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever a page is visited.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
@ -10,8 +12,10 @@ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,6 +1,8 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever the server handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever the server handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
@ -10,8 +12,10 @@ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -39,6 +39,7 @@ export const useAppApi = ({ url, options, skip }: UseFetchProps) => {
} }
const json = await res.json(); const json = await res.json();
setData(json); setData(json);
} catch (e) { } catch (e) {
setError(e as unknown); setError(e as unknown);

View file

@ -5,6 +5,7 @@ interface EmailServiceProvider {
export const Klaviyo = (token: string): EmailServiceProvider => ({ export const Klaviyo = (token: string): EmailServiceProvider => ({
send: async (event, recipient, context) => { send: async (event, recipient, context) => {
const formParams = new URLSearchParams(); const formParams = new URLSearchParams();
formParams.append( formParams.append(
"data", "data",
JSON.stringify({ JSON.stringify({

View file

@ -8,10 +8,12 @@ import {
} from "../../generated/graphql"; } from "../../generated/graphql";
import { settingsManagerSecretKey } from "../../saleor-app"; import { settingsManagerSecretKey } from "../../saleor-app";
// Function is using urql graphql client to fetch all available metadata. /*
// Before returning query result, we are transforming response to list of objects with key and value fields * Function is using urql graphql client to fetch all available metadata.
// which can be used by the manager. * Before returning query result, we are transforming response to list of objects with key and value fields
// Result of this query is cached by the manager. * which can be used by the manager.
* Result of this query is cached by the manager.
*/
export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> { export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> {
const { error, data } = await client const { error, data } = await client
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {}) .query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
@ -25,9 +27,11 @@ export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]>
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || []; return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
} }
// Mutate function takes urql client and metadata entries, and construct mutation to the API. /*
// Before data are send, additional query for required App ID is made. * Mutate function takes urql client and metadata entries, and construct mutation to the API.
// The manager will use updated entries returned by this mutation to update it's cache. * Before data are send, additional query for required App ID is made.
* The manager will use updated entries returned by this mutation to update it's cache.
*/
export async function mutateMetadata(client: Client, appId: string, metadata: MetadataEntry[]) { export async function mutateMetadata(client: Client, appId: string, metadata: MetadataEntry[]) {
const { error: mutationError, data: mutationData } = await client const { error: mutationError, data: mutationData } = await client
.mutation(UpdateAppMetadataDocument, { .mutation(UpdateAppMetadataDocument, {
@ -50,9 +54,11 @@ export async function mutateMetadata(client: Client, appId: string, metadata: Me
} }
export const createSettingsManager = (client: Client, appId: string) => export const createSettingsManager = (client: Client, appId: string) =>
// EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. /*
// We recommend it for production, because all values are encrypted. * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
// If your use case require plain text values, you can use MetadataManager. * We recommend it for production, because all values are encrypted.
* If your use case require plain text values, you can use MetadataManager.
*/
new EncryptedMetadataManager({ new EncryptedMetadataManager({
// Secret key should be randomly created for production and set as environment variable // Secret key should be randomly created for production and set as environment variable
encryptionKey: settingsManagerSecretKey, encryptionKey: settingsManagerSecretKey,

View file

@ -73,6 +73,7 @@ function SaleorApp({ Component, pageProps }: AppLayoutProps) {
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -15,10 +15,12 @@ interface AppErrorProps extends ErrorProps {
function MyError({ statusCode, hasGetInitialPropsRun, err }: ErrorPageProps) { function MyError({ statusCode, hasGetInitialPropsRun, err }: ErrorPageProps) {
if (!hasGetInitialPropsRun && err) { if (!hasGetInitialPropsRun && err) {
// getInitialProps is not called when an exception is thrown /*
// at the top level of a module while it is being loaded. * getInitialProps is not called when an exception is thrown
// As a workaround, we pass err via _app.js so it can be captured * at the top level of a module while it is being loaded.
// Read more: https://github.com/vercel/next.js/issues/8592. * As a workaround, we pass err via _app.js so it can be captured
* Read more: https://github.com/vercel/next.js/issues/8592.
*/
Sentry.captureException(err); Sentry.captureException(err);
// Flushing is not required in this case as it only happens on the client // Flushing is not required in this case as it only happens on the client
} }
@ -31,8 +33,10 @@ MyError.getInitialProps = async (context: NextPageContext) => {
const { res, err, asPath } = context; const { res, err, asPath } = context;
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when /*
// getInitialProps has run * Workaround for https://github.com/vercel/next.js/issues/8592, mark when
* getInitialProps has run
*/
errorInitialProps.hasGetInitialPropsRun = true; errorInitialProps.hasGetInitialPropsRun = true;
// Returning early because we don't want to log 404 errors to Sentry. // Returning early because we don't want to log 404 errors to Sentry.
@ -40,32 +44,38 @@ MyError.getInitialProps = async (context: NextPageContext) => {
return errorInitialProps; return errorInitialProps;
} }
// Running on the server, the response object (`res`) is available. /*
// * Running on the server, the response object (`res`) is available.
// Next.js will pass an err on the server if a page's data fetching methods *
// threw or returned a Promise that rejected * Next.js will pass an err on the server if a page's data fetching methods
// * threw or returned a Promise that rejected
// Running on the client (browser), Next.js will provide an err if: *
// * Running on the client (browser), Next.js will provide an err if:
// - a page's `getInitialProps` threw or returned a Promise that rejected *
// - an exception was thrown somewhere in the React lifecycle (render, * - a page's `getInitialProps` threw or returned a Promise that rejected
// componentDidMount, etc) that was caught by Next.js's React Error * - an exception was thrown somewhere in the React lifecycle (render,
// Boundary. Read more about what types of exceptions are caught by Error * componentDidMount, etc) that was caught by Next.js's React Error
// Boundaries: https://reactjs.org/docs/error-boundaries.html * Boundary. Read more about what types of exceptions are caught by Error
* Boundaries: https://reactjs.org/docs/error-boundaries.html
*/
if (err) { if (err) {
Sentry.captureException(err); Sentry.captureException(err);
// Flushing before returning is necessary if deploying to Vercel, see /*
// https://vercel.com/docs/platform/limits#streaming-responses * Flushing before returning is necessary if deploying to Vercel, see
* https://vercel.com/docs/platform/limits#streaming-responses
*/
await Sentry.flush(2000); await Sentry.flush(2000);
return errorInitialProps; return errorInitialProps;
} }
// If this point is reached, getInitialProps was called without any /*
// information about what the error might be. This is unexpected and may * If this point is reached, getInitialProps was called without any
// indicate a bug introduced in Next.js, so record it in Sentry * information about what the error might be. This is unexpected and may
* indicate a bug introduced in Next.js, so record it in Sentry
*/
Sentry.captureException(new Error(`_error.js getInitialProps missing data at path: ${asPath}`)); Sentry.captureException(new Error(`_error.js getInitialProps missing data at path: ${asPath}`));
await Sentry.flush(2000); await Sentry.flush(2000);

View file

@ -91,6 +91,7 @@ const handler: NextWebhookApiHandler<CustomerCreatedWebhookPayloadFragment> = as
if (klaviyoResponse.status !== 200) { if (klaviyoResponse.status !== 200) {
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || ""; const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
console.debug("Klaviyo returned error: ", klaviyoMessage); console.debug("Klaviyo returned error: ", klaviyoMessage);
return res.status(500).json({ return res.status(500).json({

View file

@ -96,6 +96,7 @@ const handler: NextWebhookApiHandler<FulfillmentCreatedWebhookPayloadFragment> =
if (klaviyoResponse.status !== 200) { if (klaviyoResponse.status !== 200) {
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || ""; const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
console.debug("Klaviyo returned error: ", klaviyoMessage); console.debug("Klaviyo returned error: ", klaviyoMessage);
return res.status(500).json({ return res.status(500).json({

View file

@ -67,6 +67,7 @@ const handler: NextWebhookApiHandler<OrderCreatedWebhookPayloadFragment> = async
if (klaviyoResponse.status !== 200) { if (klaviyoResponse.status !== 200) {
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || ""; const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
console.debug("Klaviyo returned error: ", klaviyoMessage); console.debug("Klaviyo returned error: ", klaviyoMessage);
return res.status(500).json({ return res.status(500).json({
success: false, success: false,

View file

@ -67,6 +67,7 @@ const handler: NextWebhookApiHandler<OrderFullyPaidWebhookPayloadFragment> = asy
if (klaviyoResponse.status !== 200) { if (klaviyoResponse.status !== 200) {
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || ""; const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
console.debug("Klaviyo returned error: ", klaviyoMessage); console.debug("Klaviyo returned error: ", klaviyoMessage);
return res.status(500).json({ return res.status(500).json({

View file

@ -171,6 +171,7 @@ function Configuration() {
const onChange = (event: ChangeEvent) => { const onChange = (event: ChangeEvent) => {
const { name, value } = event.target as HTMLInputElement; const { name, value } = event.target as HTMLInputElement;
setConfiguration((prev) => setConfiguration((prev) =>
prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField)) prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField))
); );

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest" "test": "vitest"
}, },
@ -55,9 +56,5 @@
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"prettier": "^2.8.2", "prettier": "^2.8.2",
"typescript": "4.9.4" "typescript": "4.9.4"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -69,6 +69,7 @@ function NextApp({ Component, pageProps }: AppProps) {
*/ */
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest" "test": "vitest"
@ -69,9 +70,5 @@
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"prettier": "^2.8.2", "prettier": "^2.8.2",
"typescript": "4.9.4" "typescript": "4.9.4"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -86,5 +86,6 @@ export const generateGoogleXmlFeed = ({
}, },
}, },
]; ];
return builder.build(data); return builder.build(data);
}; };

View file

@ -81,9 +81,11 @@ export async function mutateMetadata(client: Client, metadata: MetadataEntry[])
} }
export const createSettingsManager = (client: Client) => { export const createSettingsManager = (client: Client) => {
// EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. /*
// We recommend it for production, because all values are encrypted. * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
// If your use case require plain text values, you can use MetadataManager. * We recommend it for production, because all values are encrypted.
* If your use case require plain text values, you can use MetadataManager.
*/
return new EncryptedMetadataManager({ return new EncryptedMetadataManager({
// Secret key should be randomly created for production and set as environment variable // Secret key should be randomly created for production and set as environment variable
encryptionKey: process.env.SECRET_KEY!, encryptionKey: process.env.SECRET_KEY!,

View file

@ -36,6 +36,7 @@ export const FeedPreviewCard = ({ channelSlug }: FeedPreviewCardProps) => {
const openUrlInNewTab = async (url: string) => { const openUrlInNewTab = async (url: string) => {
await appBridge?.dispatch(actions.Redirect({ to: url, newContext: true })); await appBridge?.dispatch(actions.Redirect({ to: url, newContext: true }));
}; };
return ( return (
<Paper elevation={0} className={styles.instructionsContainer}> <Paper elevation={0} className={styles.instructionsContainer}>
<Typography paragraph variant="h3"> <Typography paragraph variant="h3">

View file

@ -65,6 +65,7 @@ export const SideMenu: React.FC<SideMenuProps> = ({
const classes = useStyles(); const classes = useStyles();
const isNoItems = !items || !items.length; const isNoItems = !items || !items.length;
return ( return (
<Card className={classes.menu}> <Card className={classes.menu}>
<CardHeader title={title} action={headerToolbar} /> <CardHeader title={title} action={headerToolbar} />

View file

@ -38,6 +38,7 @@ export const categoryMappingRouter = router({
.input(SetCategoryMappingInputSchema) .input(SetCategoryMappingInputSchema)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl }); const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
logger.debug("categoriesRouter.setCategoryMapping called"); logger.debug("categoriesRouter.setCategoryMapping called");
const { error } = await ctx.apiClient const { error } = await ctx.apiClient
.mutation(UpdateCategoryMappingDocument, { .mutation(UpdateCategoryMappingDocument, {

View file

@ -28,10 +28,12 @@ export const ConfigurationPageBaseLayout = ({ children }: Props) => {
const navigateToTab = (value: string) => { const navigateToTab = (value: string) => {
const redirectionUrl = tabs.find((tab) => tab.key === value)?.url; const redirectionUrl = tabs.find((tab) => tab.key === value)?.url;
if (redirectionUrl) { if (redirectionUrl) {
router.push(redirectionUrl); router.push(redirectionUrl);
} }
}; };
return ( return (
<div className={styles.appContainer}> <div className={styles.appContainer}>
<PageTabs value={activePath} onChange={navigateToTab}> <PageTabs value={activePath} onChange={navigateToTab}>

View file

@ -63,6 +63,7 @@ const generateClassName = createGenerateClassName({
* Ensure instance is a singleton. * Ensure instance is a singleton.
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk * TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
*/ */
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined; export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
/** /**
@ -78,6 +79,7 @@ function NextApp({ Component, pageProps }: AppProps) {
*/ */
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -22,6 +22,7 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
channel, channel,
route: "api/feed/{url}/{channel}/google.xml", route: "api/feed/{url}/{channel}/google.xml",
}); });
logger.debug("Feed route visited"); logger.debug("Feed route visited");
if (!url.length) { if (!url.length) {
@ -59,8 +60,10 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
let storefrontUrl: string; let storefrontUrl: string;
let productStorefrontUrl: string; let productStorefrontUrl: string;
try { try {
const settings = await getGoogleFeedSettings({ authData, channel }); const settings = await getGoogleFeedSettings({ authData, channel });
storefrontUrl = settings.storefrontUrl; storefrontUrl = settings.storefrontUrl;
productStorefrontUrl = settings.productStorefrontUrl; productStorefrontUrl = settings.productStorefrontUrl;
} catch (error) { } catch (error) {
@ -72,8 +75,10 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
let shopName: string; let shopName: string;
let shopDescription: string | undefined; let shopDescription: string | undefined;
try { try {
const shopDetails = await fetchShopData({ client, channel }); const shopDetails = await fetchShopData({ client, channel });
shopName = shopDetails.shopName; shopName = shopDetails.shopName;
shopDescription = shopDetails.shopDescription; shopDescription = shopDetails.shopDescription;
} catch (error) { } catch (error) {

View file

@ -1,7 +1,9 @@
// This file sets a custom webpack configuration to use your Next.js app /*
// with Sentry. * This file sets a custom webpack configuration to use your Next.js app
// https://nextjs.org/docs/api-reference/next.config.js/introduction * with Sentry.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * https://nextjs.org/docs/api-reference/next.config.js/introduction
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
const { withSentryConfig } = require("@sentry/nextjs"); const { withSentryConfig } = require("@sentry/nextjs");
@ -23,12 +25,14 @@ const moduleExports = {
}, },
transpilePackages: ["@saleor/apps-shared"], transpilePackages: ["@saleor/apps-shared"],
sentry: { sentry: {
// Use `hidden-source-map` rather than `source-map` as the Webpack `devtool` /*
// for client-side builds. (This will be the default starting in * Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
// `@sentry/nextjs` version 8.0.0.) See * for client-side builds. (This will be the default starting in
// https://webpack.js.org/configuration/devtool/ and * `@sentry/nextjs` version 8.0.0.) See
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map * https://webpack.js.org/configuration/devtool/ and
// for more information. * https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
* for more information.
*/
hideSourceMaps: true, hideSourceMaps: true,
disableServerWebpackPlugin: !isSentryPropertiesInEnvironment, disableServerWebpackPlugin: !isSentryPropertiesInEnvironment,
disableClientWebpackPlugin: !isSentryPropertiesInEnvironment, disableClientWebpackPlugin: !isSentryPropertiesInEnvironment,
@ -36,17 +40,23 @@ const moduleExports = {
}; };
const sentryWebpackPluginOptions = { const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that /*
// the following options are set automatically, and overriding them is not * Additional config options for the Sentry Webpack plugin. Keep in mind that
// recommended: * the following options are set automatically, and overriding them is not
// release, url, org, project, authToken, configFile, stripPrefix, * recommended:
// urlPrefix, include, ignore * release, url, org, project, authToken, configFile, stripPrefix,
* urlPrefix, include, ignore
*/
silent: true, // Suppresses all logs silent: true, // Suppresses all logs
// For all available options, see: /*
// https://github.com/getsentry/sentry-webpack-plugin#options. * For all available options, see:
* https://github.com/getsentry/sentry-webpack-plugin#options.
*/
}; };
// Make sure adding Sentry options is the last code to run before exporting, to /*
// ensure that your source maps include changes from all other Webpack plugins * Make sure adding Sentry options is the last code to run before exporting, to
* ensure that your source maps include changes from all other Webpack plugins
*/
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions); module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen" "generate": "graphql-codegen"
}, },
@ -58,9 +59,5 @@
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"typescript": "4.8.4" "typescript": "4.8.4"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the browser. /*
// The config you add here will be used whenever a page is visited. * This file configures the initialization of Sentry on the browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever a page is visited.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever middleware or an Edge route handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever middleware or an Edge route handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,17 +1,21 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever the server handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever the server handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN
Sentry.init({ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -49,8 +49,10 @@ export const AlgoliaConfigurationCard = () => {
}, },
body: JSON.stringify(conf), body: JSON.stringify(conf),
}); });
if (resp.status >= 200 && resp.status < 300) { if (resp.status >= 200 && resp.status < 300) {
const data = (await resp.json()) as { data?: AlgoliaConfigurationFields }; const data = (await resp.json()) as { data?: AlgoliaConfigurationFields };
return data.data; return data.data;
} }
throw new Error(`Server responded with status code ${resp.status}`); throw new Error(`Server responded with status code ${resp.status}`);

View file

@ -20,6 +20,7 @@ function Hit(props: { hit: any }) {
export function Hits() { export function Hits() {
const { hits } = useHits(); const { hits } = useHits();
return ( return (
<div className={styles.hitsWrapper}> <div className={styles.hitsWrapper}>
{hits.map((hit) => ( {hits.map((hit) => (

View file

@ -10,6 +10,7 @@ export function SearchBox() {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
refine(e.target.value); refine(e.target.value);
}; };
return ( return (
<div className={styles.textFieldContainer}> <div className={styles.textFieldContainer}>
<TextField <TextField

View file

@ -66,6 +66,7 @@ export const useQueryAllProducts = (paused: boolean) => {
(async () => { (async () => {
const channels = await getChannels(); const channels = await getChannels();
// get all products for each channel // get all products for each channel
await channels.data?.channels?.reduce(async (acc, channel) => { await channels.data?.channels?.reduce(async (acc, channel) => {
await acc; await acc;
await getProducts(channel.slug, ""); await getProducts(channel.slug, "");

View file

@ -34,6 +34,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
return Promise.all( return Promise.all(
Object.entries(groupedByIndex).map(([indexName, objects]) => { Object.entries(groupedByIndex).map(([indexName, objects]) => {
const index = this.#algolia.initIndex(indexName); const index = this.#algolia.initIndex(indexName);
return index.saveObjects(objects); return index.saveObjects(objects);
}) })
); );
@ -45,6 +46,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
return Promise.all( return Promise.all(
Object.entries(groupedByIndex).map(([indexName, objects]) => { Object.entries(groupedByIndex).map(([indexName, objects]) => {
const index = this.#algolia.initIndex(indexName); const index = this.#algolia.initIndex(indexName);
return index.deleteObjects(objects.map((o) => o.objectID)); return index.deleteObjects(objects.map((o) => o.objectID));
}) })
); );
@ -57,6 +59,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
visibleInListings: true, visibleInListings: true,
indexNamePrefix: this.#indexNamePrefix, indexNamePrefix: this.#indexNamePrefix,
}); });
await this.saveGroupedByIndex(groupedByIndex); await this.saveGroupedByIndex(groupedByIndex);
} }
@ -128,6 +131,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
visibleInListings: null, visibleInListings: null,
indexNamePrefix: this.#indexNamePrefix, indexNamePrefix: this.#indexNamePrefix,
}); });
if (groupedByIndexToDelete) { if (groupedByIndexToDelete) {
await this.deleteGroupedByIndex(groupedByIndexToDelete); await this.deleteGroupedByIndex(groupedByIndexToDelete);
} }
@ -157,6 +161,7 @@ const groupVariantByIndexName = (
variant: productVariant, variant: productVariant,
channel: channelListing.channel.slug, channel: channelListing.channel.slug,
}); });
return { return {
object, object,
indexName: channelListingToAlgoliaIndexId(channelListing, indexNamePrefix), indexName: channelListingToAlgoliaIndexId(channelListing, indexNamePrefix),
@ -190,5 +195,6 @@ const groupProductsByIndexName = (
acc[indexName].push(...objects); acc[indexName].push(...objects);
return acc; return acc;
}, {} as GroupedByIndex); }, {} as GroupedByIndex);
return groupedByIndex; return groupedByIndex;
}; };

View file

@ -21,6 +21,7 @@ export function channelListingToAlgoliaIndexId(
channelListing.channel.currencyCode, channelListing.channel.currencyCode,
"products", "products",
]; ];
return nameSegments.filter(isNotNil).join("."); return nameSegments.filter(isNotNil).join(".");
} }
@ -85,6 +86,7 @@ export function productAndVariantToAlgolia({
const attributes = { const attributes = {
...product.attributes.reduce((acc, attr, idx) => { ...product.attributes.reduce((acc, attr, idx) => {
const preparedAttr = mapSelectedAttributesToRecord(attr); const preparedAttr = mapSelectedAttributesToRecord(attr);
if (!preparedAttr) { if (!preparedAttr) {
return acc; return acc;
} }
@ -95,6 +97,7 @@ export function productAndVariantToAlgolia({
}, {}), }, {}),
...variant.attributes.reduce((acc, attr, idx) => { ...variant.attributes.reduce((acc, attr, idx) => {
const preparedAttr = mapSelectedAttributesToRecord(attr); const preparedAttr = mapSelectedAttributesToRecord(attr);
if (!preparedAttr) { if (!preparedAttr) {
return acc; return acc;
} }
@ -123,6 +126,7 @@ export function productAndVariantToAlgolia({
collections: product.collections?.map((collection) => collection.name) || [], collections: product.collections?.map((collection) => collection.name) || [],
metadata: formatMetadata(variant), metadata: formatMetadata(variant),
}; };
return document; return document;
} }

View file

@ -18,6 +18,7 @@ export const getAlgoliaConfiguration = async ({ authData }: GetAlgoliaConfigurat
try { try {
const secretKey = await settings.get("secretKey", authData.domain); const secretKey = await settings.get("secretKey", authData.domain);
if (!secretKey?.length) { if (!secretKey?.length) {
return { return {
errors: [ errors: [
@ -30,6 +31,7 @@ export const getAlgoliaConfiguration = async ({ authData }: GetAlgoliaConfigurat
} }
const appId = await settings.get("appId", authData.domain); const appId = await settings.get("appId", authData.domain);
if (!appId?.length) { if (!appId?.length) {
return { return {
errors: [ errors: [
@ -41,6 +43,7 @@ export const getAlgoliaConfiguration = async ({ authData }: GetAlgoliaConfigurat
} }
const indexNamePrefix = (await settings.get("indexNamePrefix", authData.domain)) || ""; const indexNamePrefix = (await settings.get("indexNamePrefix", authData.domain)) || "";
debug("Configuration fetched"); debug("Configuration fetched");
return { return {
settings: { settings: {

View file

@ -9,6 +9,7 @@ export const fetchConfiguration = async (saleorApiUrl: string, token: string) =>
}, },
}); });
const data = (await res.json()) as { data?: AlgoliaConfigurationFields }; const data = (await res.json()) as { data?: AlgoliaConfigurationFields };
return data.data; return data.data;
}; };

View file

@ -8,10 +8,12 @@ import {
} from "../../generated/graphql"; } from "../../generated/graphql";
import { settingsManagerSecretKey } from "../../saleor-app"; import { settingsManagerSecretKey } from "../../saleor-app";
// Function is using urql graphql client to fetch all available metadata. /*
// Before returning query result, we are transforming response to list of objects with key and value fields * Function is using urql graphql client to fetch all available metadata.
// which can be used by the manager. * Before returning query result, we are transforming response to list of objects with key and value fields
// Result of this query is cached by the manager. * which can be used by the manager.
* Result of this query is cached by the manager.
*/
export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> { export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> {
const { error, data } = await client const { error, data } = await client
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {}) .query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
@ -25,9 +27,11 @@ export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]>
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || []; return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
} }
// Mutate function takes urql client and metadata entries, and construct mutation to the API. /*
// Before data are send, additional query for required App ID is made. * Mutate function takes urql client and metadata entries, and construct mutation to the API.
// The manager will use updated entries returned by this mutation to update it's cache. * Before data are send, additional query for required App ID is made.
* The manager will use updated entries returned by this mutation to update it's cache.
*/
export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) { export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) {
// to update the metadata, ID is required // to update the metadata, ID is required
const { error: idQueryError, data: idQueryData } = await client const { error: idQueryError, data: idQueryData } = await client
@ -69,9 +73,11 @@ export async function mutateMetadata(client: Client, metadata: MetadataEntry[])
} }
export const createSettingsManager = (client: Client) => { export const createSettingsManager = (client: Client) => {
// EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. /*
// We recommend it for production, because all values are encrypted. * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
// If your use case require plain text values, you can use MetadataManager. * We recommend it for production, because all values are encrypted.
* If your use case require plain text values, you can use MetadataManager.
*/
return new EncryptedMetadataManager({ return new EncryptedMetadataManager({
// Secret key should be randomly created for production and set as environment variable // Secret key should be randomly created for production and set as environment variable
encryptionKey: settingsManagerSecretKey, encryptionKey: settingsManagerSecretKey,

View file

@ -35,6 +35,7 @@ const queryClient = new QueryClient({
}, },
}, },
}); });
type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>; type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>;
/** /**
@ -73,6 +74,7 @@ function NextApp({ Component, pageProps }: AppProps) {
*/ */
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -20,16 +20,20 @@ import * as Sentry from '@sentry/nextjs';
import NextErrorComponent from 'next/error'; import NextErrorComponent from 'next/error';
const CustomErrorComponent = props => { const CustomErrorComponent = props => {
// If you're using a Nextjs version prior to 12.2.1, uncomment this to /*
// compensate for https://github.com/vercel/next.js/issues/8592 * If you're using a Nextjs version prior to 12.2.1, uncomment this to
// Sentry.captureUnderscoreErrorException(props); * compensate for https://github.com/vercel/next.js/issues/8592
* Sentry.captureUnderscoreErrorException(props);
*/
return <NextErrorComponent statusCode={props.statusCode} />; return <NextErrorComponent statusCode={props.statusCode} />;
}; };
CustomErrorComponent.getInitialProps = async contextData => { CustomErrorComponent.getInitialProps = async contextData => {
// In case this is running in a serverless function, await this in order to give Sentry /*
// time to send the error before the lambda exits * In case this is running in a serverless function, await this in order to give Sentry
* time to send the error before the lambda exits
*/
await Sentry.captureUnderscoreErrorException(contextData); await Sentry.captureUnderscoreErrorException(contextData);
// This will contain the status code of the response // This will contain the status code of the response

View file

@ -58,6 +58,7 @@ export const handler = async (
const { appId, searchKey, secretKey, indexNamePrefix } = JSON.parse( const { appId, searchKey, secretKey, indexNamePrefix } = JSON.parse(
req.body req.body
) as AlgoliaConfigurationFields; ) as AlgoliaConfigurationFields;
await settings.set([ await settings.set([
{ key: "secretKey", value: secretKey || "", domain }, { key: "secretKey", value: secretKey || "", domain },
{ key: "searchKey", value: searchKey || "", domain }, { key: "searchKey", value: searchKey || "", domain },

View file

@ -22,6 +22,7 @@ export const handler: NextWebhookApiHandler<ProductCreated> = async (req, res, c
const debug = createDebug(`Webhook handler - ${webhookProductCreated.event}`); const debug = createDebug(`Webhook handler - ${webhookProductCreated.event}`);
const { event, authData } = context; const { event, authData } = context;
debug( debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!` `New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
); );
@ -43,6 +44,7 @@ export const handler: NextWebhookApiHandler<ProductCreated> = async (req, res, c
}); });
const { product } = context.payload; const { product } = context.payload;
if (product) { if (product) {
await searchProvider.createProduct(product); await searchProvider.createProduct(product);
} }

View file

@ -22,6 +22,7 @@ export const handler: NextWebhookApiHandler<ProductDeleted> = async (req, res, c
const debug = createDebug(`Webhook handler - ${webhookProductDeleted.event}`); const debug = createDebug(`Webhook handler - ${webhookProductDeleted.event}`);
const { event, authData } = context; const { event, authData } = context;
debug( debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!` `New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
); );
@ -43,6 +44,7 @@ export const handler: NextWebhookApiHandler<ProductDeleted> = async (req, res, c
}); });
const { product } = context.payload; const { product } = context.payload;
if (product) { if (product) {
await searchProvider.deleteProduct(product); await searchProvider.deleteProduct(product);
} }

View file

@ -22,6 +22,7 @@ export const handler: NextWebhookApiHandler<ProductUpdated> = async (req, res, c
const debug = createDebug(`Webhook handler - ${webhookProductUpdated.event}`); const debug = createDebug(`Webhook handler - ${webhookProductUpdated.event}`);
const { event, authData } = context; const { event, authData } = context;
debug( debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!` `New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
); );
@ -43,6 +44,7 @@ export const handler: NextWebhookApiHandler<ProductUpdated> = async (req, res, c
}); });
const { product } = context.payload; const { product } = context.payload;
if (product) { if (product) {
await searchProvider.updateProduct(product); await searchProvider.updateProduct(product);
} }

View file

@ -25,6 +25,7 @@ export const handler: NextWebhookApiHandler<ProductVariantCreated> = async (req,
const debug = createDebug(`Webhook handler - ${webhookProductVariantCreated.event}`); const debug = createDebug(`Webhook handler - ${webhookProductVariantCreated.event}`);
const { event, authData } = context; const { event, authData } = context;
debug( debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!` `New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
); );
@ -46,6 +47,7 @@ export const handler: NextWebhookApiHandler<ProductVariantCreated> = async (req,
}); });
const { productVariant } = context.payload; const { productVariant } = context.payload;
if (productVariant) { if (productVariant) {
await searchProvider.createProductVariant(productVariant); await searchProvider.createProductVariant(productVariant);
} }

View file

@ -25,6 +25,7 @@ export const handler: NextWebhookApiHandler<ProductVariantDeleted> = async (req,
const debug = createDebug(`Webhook handler - ${webhookProductVariantDeleted.event}`); const debug = createDebug(`Webhook handler - ${webhookProductVariantDeleted.event}`);
const { event, authData } = context; const { event, authData } = context;
debug( debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!` `New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
); );
@ -46,6 +47,7 @@ export const handler: NextWebhookApiHandler<ProductVariantDeleted> = async (req,
}); });
const { productVariant } = context.payload; const { productVariant } = context.payload;
if (productVariant) { if (productVariant) {
await searchProvider.deleteProductVariant(productVariant); await searchProvider.deleteProductVariant(productVariant);
} }

View file

@ -25,6 +25,7 @@ export const handler: NextWebhookApiHandler<ProductVariantUpdated> = async (req,
const debug = createDebug(`Webhook handler - ${webhookProductVariantUpdated.event}`); const debug = createDebug(`Webhook handler - ${webhookProductVariantUpdated.event}`);
const { event, authData } = context; const { event, authData } = context;
debug( debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!` `New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
); );
@ -46,6 +47,7 @@ export const handler: NextWebhookApiHandler<ProductVariantUpdated> = async (req,
}); });
const { productVariant } = context.payload; const { productVariant } = context.payload;
if (productVariant) { if (productVariant) {
await searchProvider.updateProductVariant(productVariant); await searchProvider.updateProductVariant(productVariant);
} }

View file

@ -11,6 +11,10 @@
"ALLOWED_DOMAIN_PATTERN", "ALLOWED_DOMAIN_PATTERN",
"REST_APL_ENDPOINT", "REST_APL_ENDPOINT",
"REST_APL_TOKEN", "REST_APL_TOKEN",
"SENTRY_AUTH_TOKEN",
"SENTRY_PROJECT",
"SENTRY_DSN",
"SENTRY_ORG",
"NEXT_PUBLIC_VERCEL_ENV" "NEXT_PUBLIC_VERCEL_ENV"
] ]
} }

View file

@ -1,7 +1,9 @@
// This file sets a custom webpack configuration to use your Next.js app /*
// with Sentry. * This file sets a custom webpack configuration to use your Next.js app
// https://nextjs.org/docs/api-reference/next.config.js/introduction * with Sentry.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * https://nextjs.org/docs/api-reference/next.config.js/introduction
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
const { withSentryConfig } = require("@sentry/nextjs"); const { withSentryConfig } = require("@sentry/nextjs");
@ -18,28 +20,36 @@ const moduleExports = {
sentry: { sentry: {
disableServerWebpackPlugin: !isSentryPropertiesInEnvironment, disableServerWebpackPlugin: !isSentryPropertiesInEnvironment,
disableClientWebpackPlugin: !isSentryPropertiesInEnvironment, disableClientWebpackPlugin: !isSentryPropertiesInEnvironment,
// Use `hidden-source-map` rather than `source-map` as the Webpack `devtool` /*
// for client-side builds. (This will be the default starting in * Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
// `@sentry/nextjs` version 8.0.0.) See * for client-side builds. (This will be the default starting in
// https://webpack.js.org/configuration/devtool/ and * `@sentry/nextjs` version 8.0.0.) See
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map * https://webpack.js.org/configuration/devtool/ and
// for more information. * https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
* for more information.
*/
hideSourceMaps: true, hideSourceMaps: true,
}, },
}; };
const sentryWebpackPluginOptions = { const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that /*
// the following options are set automatically, and overriding them is not * Additional config options for the Sentry Webpack plugin. Keep in mind that
// recommended: * the following options are set automatically, and overriding them is not
// release, url, org, project, authToken, configFile, stripPrefix, * recommended:
// urlPrefix, include, ignore * release, url, org, project, authToken, configFile, stripPrefix,
* urlPrefix, include, ignore
*/
silent: true, // Suppresses all logs silent: true, // Suppresses all logs
// For all available options, see: /*
// https://github.com/getsentry/sentry-webpack-plugin#options. * For all available options, see:
* https://github.com/getsentry/sentry-webpack-plugin#options.
*/
}; };
// Make sure adding Sentry options is the last code to run before exporting, to /*
// ensure that your source maps include changes from all other Webpack plugins * Make sure adding Sentry options is the last code to run before exporting, to
* ensure that your source maps include changes from all other Webpack plugins
*/
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions); module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);

View file

@ -6,7 +6,8 @@
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev", "dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "pnpm generate && prettier --loglevel warn --write . && eslint --fix .", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen" "generate": "graphql-codegen"
}, },
@ -54,9 +55,5 @@
"postcss": "^8.4.14", "postcss": "^8.4.14",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"typescript": "4.8.3" "typescript": "4.8.3"
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{js,ts,tsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -1,6 +1,8 @@
// This file configures the initialization of Sentry on the browser. /*
// The config you add here will be used whenever a page is visited. * This file configures the initialization of Sentry on the browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever a page is visited.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
@ -10,8 +12,10 @@ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,6 +1,8 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever middleware or an Edge route handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever middleware or an Edge route handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
@ -10,8 +12,10 @@ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -1,6 +1,8 @@
// This file configures the initialization of Sentry on the server. /*
// The config you add here will be used whenever the server handles a request. * This file configures the initialization of Sentry on the server.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/ * The config you add here will be used whenever the server handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs"; import * as Sentry from "@sentry/nextjs";
@ -10,8 +12,10 @@ Sentry.init({
dsn: SENTRY_DSN, dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control // Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
// ... /*
// Note: if you want to override the automatic release value, do not set a * ...
// `release` value here - use the environment variable `SENTRY_RELEASE`, so * Note: if you want to override the automatic release value, do not set a
// that it will also get attached to your source maps * `release` value here - use the environment variable `SENTRY_RELEASE`, so
* that it will also get attached to your source maps
*/
}); });

View file

@ -12,10 +12,12 @@ function _ThemeSynchronizer() {
const { appBridgeState, appBridge } = useAppBridge(); const { appBridgeState, appBridge } = useAppBridge();
const { setTheme, themeType } = useTheme(); const { setTheme, themeType } = useTheme();
// todo - replace this hook to appBridge.subscribe and react only only on initial theme event /*
// useEffect(() =>{ * todo - replace this hook to appBridge.subscribe and react only only on initial theme event
// appBridge?.subscribe('theme',console.log) * useEffect(() =>{
// },[appBridge]) * appBridge?.subscribe('theme',console.log)
* },[appBridge])
*/
useEffect(() => { useEffect(() => {
if (!setTheme || !appBridgeState?.theme) { if (!setTheme || !appBridgeState?.theme) {

View file

@ -39,6 +39,7 @@ export const useAppApi = <D>({ url, options, skip }: UseFetchProps) => {
} }
const json = await res.json(); const json = await res.json();
setData(json); setData(json);
} catch (e) { } catch (e) {
setError(e as unknown); setError(e as unknown);

View file

@ -8,10 +8,12 @@ import {
} from "../../generated/graphql"; } from "../../generated/graphql";
import { settingsManagerSecretKey } from "./saleor-app"; import { settingsManagerSecretKey } from "./saleor-app";
// Function is using urql graphql client to fetch all available metadata. /*
// Before returning query result, we are transforming response to list of objects with key and value fields * Function is using urql graphql client to fetch all available metadata.
// which can be used by the manager. * Before returning query result, we are transforming response to list of objects with key and value fields
// Result of this query is cached by the manager. * which can be used by the manager.
* Result of this query is cached by the manager.
*/
export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> { export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> {
const { error, data } = await client const { error, data } = await client
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {}) .query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
@ -25,9 +27,11 @@ export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]>
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || []; return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
} }
// Mutate function takes urql client and metadata entries, and construct mutation to the API. /*
// Before data are send, additional query for required App ID is made. * Mutate function takes urql client and metadata entries, and construct mutation to the API.
// The manager will use updated entries returned by this mutation to update it's cache. * Before data are send, additional query for required App ID is made.
* The manager will use updated entries returned by this mutation to update it's cache.
*/
export async function mutateMetadata(client: Client, appId: string, metadata: MetadataEntry[]) { export async function mutateMetadata(client: Client, appId: string, metadata: MetadataEntry[]) {
const { error: mutationError, data: mutationData } = await client const { error: mutationError, data: mutationData } = await client
.mutation(UpdateAppMetadataDocument, { .mutation(UpdateAppMetadataDocument, {
@ -50,9 +54,11 @@ export async function mutateMetadata(client: Client, appId: string, metadata: Me
} }
export const createSettingsManager = (client: Client, appId: string) => export const createSettingsManager = (client: Client, appId: string) =>
// EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. /*
// We recommend it for production, because all values are encrypted. * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
// If your use case require plain text values, you can use MetadataManager. * We recommend it for production, because all values are encrypted.
* If your use case require plain text values, you can use MetadataManager.
*/
new EncryptedMetadataManager({ new EncryptedMetadataManager({
// Secret key should be randomly created for production and set as environment variable // Secret key should be randomly created for production and set as environment variable
encryptionKey: settingsManagerSecretKey, encryptionKey: settingsManagerSecretKey,

View file

@ -10,6 +10,7 @@ import { SaleorApp } from "@saleor/app-sdk/saleor-app";
*/ */
let apl: APL; let apl: APL;
switch (process.env.APL) { switch (process.env.APL) {
case "upstash": case "upstash":
// Require `UPSTASH_URL` and `UPSTASH_TOKEN` environment variables // Require `UPSTASH_URL` and `UPSTASH_TOKEN` environment variables
@ -30,12 +31,14 @@ switch (process.env.APL) {
apl = new FileAPL(); apl = new FileAPL();
} }
// TODO: Investigate why no-ssr-wrapper does not prevent this code from execution during the build time /*
// if (!process.env.SECRET_KEY && process.env.NODE_ENV === "production") { * TODO: Investigate why no-ssr-wrapper does not prevent this code from execution during the build time
// throw new Error( * if (!process.env.SECRET_KEY && process.env.NODE_ENV === "production") {
// "For production deployment SECRET_KEY is mandatory to use EncryptedSettingsManager." * throw new Error(
// ); * "For production deployment SECRET_KEY is mandatory to use EncryptedSettingsManager."
// } * );
* }
*/
// Use placeholder value for the development // Use placeholder value for the development
export const settingsManagerSecretKey = process.env.SECRET_KEY || "CHANGE_ME"; export const settingsManagerSecretKey = process.env.SECRET_KEY || "CHANGE_ME";

View file

@ -73,6 +73,7 @@ function SaleorApp({ Component, pageProps }: AppLayoutProps) {
useEffect(() => { useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side"); const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) { if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles); jssStyles?.parentElement?.removeChild(jssStyles);
} }

View file

@ -20,16 +20,20 @@ import * as Sentry from "@sentry/nextjs";
import NextErrorComponent from "next/error"; import NextErrorComponent from "next/error";
const CustomErrorComponent = (props) => { const CustomErrorComponent = (props) => {
// If you're using a Nextjs version prior to 12.2.1, uncomment this to /*
// compensate for https://github.com/vercel/next.js/issues/8592 * If you're using a Nextjs version prior to 12.2.1, uncomment this to
// Sentry.captureUnderscoreErrorException(props); * compensate for https://github.com/vercel/next.js/issues/8592
* Sentry.captureUnderscoreErrorException(props);
*/
return <NextErrorComponent statusCode={props.statusCode} />; return <NextErrorComponent statusCode={props.statusCode} />;
}; };
CustomErrorComponent.getInitialProps = async (contextData) => { CustomErrorComponent.getInitialProps = async (contextData) => {
// In case this is running in a serverless function, await this in order to give Sentry /*
// time to send the error before the lambda exits * In case this is running in a serverless function, await this in order to give Sentry
* time to send the error before the lambda exits
*/
await Sentry.captureUnderscoreErrorException(contextData); await Sentry.captureUnderscoreErrorException(contextData);
// This will contain the status code of the response // This will contain the status code of the response

View file

@ -15,10 +15,12 @@ interface AppErrorProps extends ErrorProps {
function MyError({ statusCode, hasGetInitialPropsRun, err }: ErrorPageProps) { function MyError({ statusCode, hasGetInitialPropsRun, err }: ErrorPageProps) {
if (!hasGetInitialPropsRun && err) { if (!hasGetInitialPropsRun && err) {
// getInitialProps is not called when an exception is thrown /*
// at the top level of a module while it is being loaded. * getInitialProps is not called when an exception is thrown
// As a workaround, we pass err via _app.js so it can be captured * at the top level of a module while it is being loaded.
// Read more: https://github.com/vercel/next.js/issues/8592. * As a workaround, we pass err via _app.js so it can be captured
* Read more: https://github.com/vercel/next.js/issues/8592.
*/
Sentry.captureException(err); Sentry.captureException(err);
// Flushing is not required in this case as it only happens on the client // Flushing is not required in this case as it only happens on the client
} }
@ -31,8 +33,10 @@ MyError.getInitialProps = async (context: NextPageContext) => {
const { res, err, asPath } = context; const { res, err, asPath } = context;
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when /*
// getInitialProps has run * Workaround for https://github.com/vercel/next.js/issues/8592, mark when
* getInitialProps has run
*/
errorInitialProps.hasGetInitialPropsRun = true; errorInitialProps.hasGetInitialPropsRun = true;
// Returning early because we don't want to log 404 errors to Sentry. // Returning early because we don't want to log 404 errors to Sentry.
@ -40,32 +44,38 @@ MyError.getInitialProps = async (context: NextPageContext) => {
return errorInitialProps; return errorInitialProps;
} }
// Running on the server, the response object (`res`) is available. /*
// * Running on the server, the response object (`res`) is available.
// Next.js will pass an err on the server if a page's data fetching methods *
// threw or returned a Promise that rejected * Next.js will pass an err on the server if a page's data fetching methods
// * threw or returned a Promise that rejected
// Running on the client (browser), Next.js will provide an err if: *
// * Running on the client (browser), Next.js will provide an err if:
// - a page's `getInitialProps` threw or returned a Promise that rejected *
// - an exception was thrown somewhere in the React lifecycle (render, * - a page's `getInitialProps` threw or returned a Promise that rejected
// componentDidMount, etc) that was caught by Next.js's React Error * - an exception was thrown somewhere in the React lifecycle (render,
// Boundary. Read more about what types of exceptions are caught by Error * componentDidMount, etc) that was caught by Next.js's React Error
// Boundaries: https://reactjs.org/docs/error-boundaries.html * Boundary. Read more about what types of exceptions are caught by Error
* Boundaries: https://reactjs.org/docs/error-boundaries.html
*/
if (err) { if (err) {
Sentry.captureException(err); Sentry.captureException(err);
// Flushing before returning is necessary if deploying to Vercel, see /*
// https://vercel.com/docs/platform/limits#streaming-responses * Flushing before returning is necessary if deploying to Vercel, see
* https://vercel.com/docs/platform/limits#streaming-responses
*/
await Sentry.flush(2000); await Sentry.flush(2000);
return errorInitialProps; return errorInitialProps;
} }
// If this point is reached, getInitialProps was called without any /*
// information about what the error might be. This is unexpected and may * If this point is reached, getInitialProps was called without any
// indicate a bug introduced in Next.js, so record it in Sentry * information about what the error might be. This is unexpected and may
* indicate a bug introduced in Next.js, so record it in Sentry
*/
Sentry.captureException(new Error(`_error.js getInitialProps missing data at path: ${asPath}`)); Sentry.captureException(new Error(`_error.js getInitialProps missing data at path: ${asPath}`));
await Sentry.flush(2000); await Sentry.flush(2000);

View file

@ -34,6 +34,7 @@ export const handler = async (
case "POST": { case "POST": {
const reqBody = req.body as PostRequestBody; const reqBody = req.body as PostRequestBody;
const newWebhookUrl = (await reqBody.data?.find((entry) => entry.key === WEBHOOK_URL))?.value; const newWebhookUrl = (await reqBody.data?.find((entry) => entry.key === WEBHOOK_URL))?.value;
if (!newWebhookUrl) { if (!newWebhookUrl) {
console.error("New value for the webhook URL has not been found"); console.error("New value for the webhook URL has not been found");
res.status(400).json({ res.status(400).json({

View file

@ -8,6 +8,7 @@ const handler = createAppRegisterHandler({
(url) => { (url) => {
if (allowedUrlsPattern) { if (allowedUrlsPattern) {
const regex = new RegExp(allowedUrlsPattern); const regex = new RegExp(allowedUrlsPattern);
if (regex.test(url)) { if (regex.test(url)) {
console.debug(`Registration from the URL ${url} has been accepted`); console.debug(`Registration from the URL ${url} has been accepted`);
return true; return true;

View file

@ -107,6 +107,7 @@ const handler: NextWebhookApiHandler<OrderCreatedWebhookPayloadFragment> = async
if (response.status !== 200) { if (response.status !== 200) {
const errorMessage = await response.text(); const errorMessage = await response.text();
console.error(`Slack API responded with code ${response.status}: ${errorMessage}`); console.error(`Slack API responded with code ${response.status}: ${errorMessage}`);
return res.status(500).send({ return res.status(500).send({

View file

@ -79,6 +79,7 @@ function Configuration() {
const onChange = (event: ChangeEvent) => { const onChange = (event: ChangeEvent) => {
const { name, value } = event.target as HTMLInputElement; const { name, value } = event.target as HTMLInputElement;
setConfiguration((prev) => setConfiguration((prev) =>
prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField)) prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField))
); );
@ -129,6 +130,7 @@ function Instructions() {
}); });
const slackUrl = new URL("https://api.slack.com/apps"); const slackUrl = new URL("https://api.slack.com/apps");
slackUrl.searchParams.append("new_app", "1"); slackUrl.searchParams.append("new_app", "1");
slackUrl.searchParams.append("manifest_json", JSON.stringify(data)); slackUrl.searchParams.append("manifest_json", JSON.stringify(data));

View file

@ -7,6 +7,7 @@
"build": "pnpm generate && next build", "build": "pnpm generate && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
"lint:fix": "eslint --fix .",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql", "fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"test": "vitest" "test": "vitest"

View file

@ -20,4 +20,5 @@ module.exports = {
}, },
}, },
], ],
ignorePatterns: ["next-env.d.ts"],
}; };

View file

@ -20,5 +20,8 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"scripts": {
"lint:fix": "eslint --fix ."
},
"main": "index.ts" "main": "index.ts"
} }

View file

@ -24,6 +24,7 @@
}, },
"generate": { "generate": {
"outputs": ["generated/"] "outputs": ["generated/"]
} },
"lint:fix": {}
} }
} }