Add Slack App git, remove invoices
This commit is contained in:
parent
54e77d495e
commit
20f439f3ce
103 changed files with 713 additions and 37013 deletions
|
@ -1,11 +0,0 @@
|
||||||
# Set dir where temp PDF will be stored. For Vercel use tmp
|
|
||||||
# https://vercel.com/guides/how-can-i-use-files-in-serverless-functions
|
|
||||||
TEMP_PDF_STORAGE_DIR=
|
|
||||||
#"fatal" | "error" | "warn" | "info" | "debug" | "trace"
|
|
||||||
APP_DEBUG=info
|
|
||||||
# Optional
|
|
||||||
# Regex pattern consumed conditionally to restrcit app installation to specific urls.
|
|
||||||
# See api/register.tsx
|
|
||||||
# Leave empty to allow all domains
|
|
||||||
# Example: "https:\/\/.*.saleor.cloud\/graphql\/" to enable Saleor Cloud APIs
|
|
||||||
ALLOWED_DOMAIN_PATTERN=
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"extends": ["custom"]
|
|
||||||
}
|
|
47
apps/invoice-hub/.gitignore
vendored
47
apps/invoice-hub/.gitignore
vendored
|
@ -1,47 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env*.local
|
|
||||||
.envfile
|
|
||||||
.saleor-app-auth.json
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
.auth_token
|
|
||||||
|
|
||||||
#editor
|
|
||||||
.vscode
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# GraphQL auto-generated
|
|
||||||
generated/
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
schema: graphql/schema.graphql
|
|
||||||
documents: [graphql/**/*.graphql, src/**/*.ts, src/**/*.tsx]
|
|
||||||
extensions:
|
|
||||||
codegen:
|
|
||||||
overwrite: true
|
|
||||||
generates:
|
|
||||||
generated/graphql.ts:
|
|
||||||
config:
|
|
||||||
dedupeFragments: true
|
|
||||||
plugins:
|
|
||||||
- typescript
|
|
||||||
- typescript-operations
|
|
||||||
- urql-introspection
|
|
||||||
- typescript-urql:
|
|
||||||
documentVariablePrefix: "Untyped"
|
|
||||||
fragmentVariablePrefix: "Untyped"
|
|
||||||
- typed-document-node
|
|
||||||
generated/schema.graphql:
|
|
||||||
plugins:
|
|
||||||
- schema-ast
|
|
|
@ -1,5 +0,0 @@
|
||||||
.next
|
|
||||||
saleor/api.tsx
|
|
||||||
pnpm-lock.yaml
|
|
||||||
graphql/schema.graphql
|
|
||||||
generated
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"singleQuote": false,
|
|
||||||
"printWidth": 100
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
BSD 3-Clause License
|
|
||||||
|
|
||||||
Copyright (c) 2020-2022, Saleor Commerce
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
* Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
-------
|
|
||||||
|
|
||||||
Unless stated otherwise, artwork included in this distribution is licensed
|
|
||||||
under the Creative Commons Attribution 4.0 International License.
|
|
||||||
|
|
||||||
You can learn more about the permitted use by visiting
|
|
||||||
https://creativecommons.org/licenses/by/4.0/
|
|
|
@ -1,114 +0,0 @@
|
||||||
# Invoice app
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Saleor API 3.10 ❗️
|
|
||||||
|
|
||||||
## About Saleor Invoices app
|
|
||||||
|
|
||||||
- Generates invoice PDF for each order after invoice is requested (e.g. via dashboard/orders)
|
|
||||||
- Uploads PDF to Saleor built-in file storage
|
|
||||||
- Allows to configure shop billing data per each channel
|
|
||||||
|
|
||||||
### Learn more about Apps
|
|
||||||
|
|
||||||
[Apps guide](https://docs.saleor.io/docs/3.x/developer/extending/apps/key-concepts)
|
|
||||||
|
|
||||||
[Configuring apps in dashboard](https://docs.saleor.io/docs/3.x/dashboard/apps)
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
Before you start, make sure you have installed:
|
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/)
|
|
||||||
- [pnpm](https://pnpm.io/)
|
|
||||||
- [Saleor CLI](https://docs.saleor.io/docs/3.x/cli) - optional, but recommended
|
|
||||||
|
|
||||||
### With CLI
|
|
||||||
|
|
||||||
The easiest way to set up a Saleor app is by using the Saleor CLI.
|
|
||||||
|
|
||||||
[Saleor CLI](https://github.com/saleor/saleor-cli) is designed to save you from the repetitive chores around Saleor development, including creating Apps. It will take the burden of spawning new apps locally, connecting them with Saleor environments, and establishing a tunnel for local development in seconds.
|
|
||||||
|
|
||||||
[Full Saleor CLI reference](https://docs.saleor.io/docs/3.x/developer/cli)
|
|
||||||
|
|
||||||
If you don't have a (free developer) Saleor Cloud account, create one with the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
saleor register
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you're ready to create your first App:
|
|
||||||
|
|
||||||
```
|
|
||||||
saleor app create [your-app-name]
|
|
||||||
```
|
|
||||||
|
|
||||||
In this step, Saleor CLI will:
|
|
||||||
|
|
||||||
- clone this repository to the specified folder
|
|
||||||
- install dependencies
|
|
||||||
- ask you whether you'd like to install the app in the selected Saleor environment
|
|
||||||
- create `.env` file
|
|
||||||
- start the app in development mode
|
|
||||||
|
|
||||||
Having your app ready, the final thing you want to establish is a tunnel with your Saleor environment. Go to your app's directory first and run:
|
|
||||||
|
|
||||||
```
|
|
||||||
saleor app tunnel
|
|
||||||
```
|
|
||||||
|
|
||||||
Your local application should be available now to the outside world (Saleor instance) for accepting all the events via webhooks.
|
|
||||||
|
|
||||||
A quick note: the next time you come back to your project, it is enough to launch your app in a standard way (and then launch your tunnel as described earlier):
|
|
||||||
|
|
||||||
```
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Without CLI
|
|
||||||
|
|
||||||
1. Install the dependencies by running:
|
|
||||||
```
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Start the local server with:
|
|
||||||
```
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Expose local environment using tunnel:
|
|
||||||
Use tunneling tools like [localtunnel](https://github.com/localtunnel/localtunnel) or [ngrok](https://ngrok.com/).
|
|
||||||
|
|
||||||
4. Install aplication at your dashboard:
|
|
||||||
|
|
||||||
If you use Saleor Cloud or your local server is exposed, you can install your app by following this link:
|
|
||||||
```
|
|
||||||
[YOUR_SALEOR_DASHBOARD_URL]/apps/install?manifestUrl=[YOUR_APP_TUNNEL_MANIFEST_URL]
|
|
||||||
```
|
|
||||||
This template host manifest at `/api/manifest`
|
|
||||||
|
|
||||||
|
|
||||||
You can also install application using GQL or command line. Follow the guide [how to install your app](https://docs.saleor.io/docs/3.x/developer/extending/apps/installing-apps#installation-using-graphql-api) to learn more.
|
|
||||||
|
|
||||||
### Generated schema and typings
|
|
||||||
|
|
||||||
Commands `build` and `dev` would generate schema and typed functions using Saleor's GraphQL endpoint. Commit the `generated` folder to your repo as they are necessary for queries and keeping track of the schema changes.
|
|
||||||
|
|
||||||
[Learn more](https://www.graphql-code-generator.com/) about GraphQL code generation.
|
|
||||||
|
|
||||||
### Storing registration data - APL
|
|
||||||
|
|
||||||
During registration process Saleor API pass the auth token to the app. With this token App can query Saleor API with privileged access (depending on requested permissions during the installation).
|
|
||||||
To store this data, app-template use a different [APL interfaces](https://github.com/saleor/saleor-app-sdk/blob/main/docs/apl.md).
|
|
||||||
|
|
||||||
The choice of the APL is done using `APL` environment variable. If value is not set, FileAPL is used. Available choices:
|
|
||||||
|
|
||||||
- `file`: no additional setup is required. Good choice for local development. Can't be used for multi tenant-apps or be deployed (not intended for production)
|
|
||||||
- `upstash`: use [Upstash](https://upstash.com/) Redis as storage method. Free account required. Can be used for development and production and supports multi-tenancy. Requires `UPSTASH_URL` and `UPSTASH_TOKEN` environment variables to be set
|
|
||||||
- `vercel`: used by deployments from the Marketplace. It's single-tenant only and only supported by Vercel deployments done with Saleor CLI. Requires `SALEOR_REGISTER_APP_URL` and `SALEOR_DEPLOYMENT_TOKEN` environment variables to be set (handled automatically by the Saleor CLI)
|
|
||||||
|
|
||||||
If you want to use your own database, you can implement your own APL. [Check the documentation to read more.](https://github.com/saleor/saleor-app-sdk/blob/main/docs/apl.md)
|
|
File diff suppressed because it is too large
Load diff
5
apps/invoice-hub/next-env.d.ts
vendored
5
apps/invoice-hub/next-env.d.ts
vendored
|
@ -1,5 +0,0 @@
|
||||||
/// <reference types="next" />
|
|
||||||
/// <reference types="next/image-types/global" />
|
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
|
||||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
|
|
@ -1,47 +0,0 @@
|
||||||
// This file sets a custom webpack configuration to use your Next.js app
|
|
||||||
// with Sentry.
|
|
||||||
// https://nextjs.org/docs/api-reference/next.config.js/introduction
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
||||||
|
|
||||||
const { withSentryConfig } = require("@sentry/nextjs");
|
|
||||||
|
|
||||||
const isSentryPropertiesInEnvironment =
|
|
||||||
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type import("next").NextConfig
|
|
||||||
*/
|
|
||||||
const moduleExports = {
|
|
||||||
transpilePackages: ["@saleor/shared"],
|
|
||||||
sentry: {
|
|
||||||
// Use `hidden-source-map` rather than `source-map` as the Webpack `devtool`
|
|
||||||
// for client-side builds. (This will be the default starting in
|
|
||||||
// `@sentry/nextjs` version 8.0.0.) See
|
|
||||||
// https://webpack.js.org/configuration/devtool/ and
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map
|
|
||||||
// for more information.
|
|
||||||
hideSourceMaps: true,
|
|
||||||
disableServerWebpackPlugin: !isSentryPropertiesInEnvironment,
|
|
||||||
disableClientWebpackPlugin: !isSentryPropertiesInEnvironment,
|
|
||||||
},
|
|
||||||
eslint: {
|
|
||||||
ignoreDuringBuilds: true,
|
|
||||||
},
|
|
||||||
reactStrictMode: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
// recommended:
|
|
||||||
// release, url, org, project, authToken, configFile, stripPrefix,
|
|
||||||
// urlPrefix, include, ignore
|
|
||||||
|
|
||||||
silent: true, // Suppresses all logs
|
|
||||||
// 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
|
|
||||||
module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
|
|
|
@ -1,79 +0,0 @@
|
||||||
{
|
|
||||||
"name": "saleor-app-invoice",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
|
||||||
"build": "pnpm generate && next build",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint",
|
|
||||||
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
|
|
||||||
"generate": "graphql-codegen",
|
|
||||||
"test": "vitest",
|
|
||||||
"test:ci": "CI=true vitest --coverage"
|
|
||||||
},
|
|
||||||
"saleor": {
|
|
||||||
"schemaVersion": "3.10"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@saleor/shared": "workspace:*",
|
|
||||||
"@material-ui/core": "^4.12.4",
|
|
||||||
"@material-ui/icons": "^4.11.3",
|
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
|
||||||
"@saleor/app-sdk": "0.26.0",
|
|
||||||
"@saleor/macaw-ui": "^0.7.2",
|
|
||||||
"@sentry/nextjs": "^7.31.1",
|
|
||||||
"@tanstack/react-query": "^4.19.1",
|
|
||||||
"@trpc/client": "^10.4.3",
|
|
||||||
"@trpc/next": "^10.4.3",
|
|
||||||
"@trpc/react-query": "^10.4.3",
|
|
||||||
"@trpc/server": "^10.4.3",
|
|
||||||
"@urql/exchange-auth": "^1.0.0",
|
|
||||||
"@urql/exchange-multipart-fetch": "^1.0.1",
|
|
||||||
"@web-std/file": "^3.0.2",
|
|
||||||
"clsx": "^1.2.1",
|
|
||||||
"graphql": "^16.6.0",
|
|
||||||
"graphql-tag": "^2.12.6",
|
|
||||||
"microinvoice": "^1.0.6",
|
|
||||||
"next": "13.1.3",
|
|
||||||
"pino": "^8.8.0",
|
|
||||||
"pino-pretty": "^9.1.1",
|
|
||||||
"puppeteer": "^19.4.0",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-hook-form": "^7.41.0",
|
|
||||||
"tiny-invariant": "^1.3.1",
|
|
||||||
"urql": "^3.0.3",
|
|
||||||
"usehooks-ts": "^2.9.1",
|
|
||||||
"zod": "^3.19.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@graphql-codegen/cli": "2.13.3",
|
|
||||||
"@graphql-codegen/introspection": "2.2.1",
|
|
||||||
"@graphql-codegen/typed-document-node": "^2.3.3",
|
|
||||||
"@graphql-codegen/typescript": "2.7.3",
|
|
||||||
"@graphql-codegen/typescript-operations": "2.5.3",
|
|
||||||
"@graphql-codegen/typescript-urql": "^3.7.0",
|
|
||||||
"@graphql-codegen/urql-introspection": "2.2.1",
|
|
||||||
"@graphql-typed-document-node/core": "^3.1.1",
|
|
||||||
"@types/node": "^18.8.1",
|
|
||||||
"@types/react": "^18.0.21",
|
|
||||||
"@types/react-dom": "^18.0.6",
|
|
||||||
"@types/rimraf": "^3.0.2",
|
|
||||||
"@vitejs/plugin-react": "^3.0.0",
|
|
||||||
"@vitest/coverage-c8": "^0.25.7",
|
|
||||||
"eslint": "8.25.0",
|
|
||||||
"eslint-config-next": "13.1.3",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"jsdom": "^20.0.3",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"typescript": "4.9.4",
|
|
||||||
"vite": "^4.0.0",
|
|
||||||
"vitest": "^0.25.7"
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*.{js,ts,tsx}": "eslint --cache --fix",
|
|
||||||
"*.{js,ts,tsx,css,md,json}": "prettier --write"
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,39 +0,0 @@
|
||||||
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL, VercelAPL } from "@saleor/app-sdk/APL";
|
|
||||||
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
|
|
||||||
|
|
||||||
const aplType = process.env.APL ?? "file";
|
|
||||||
|
|
||||||
let apl: APL;
|
|
||||||
|
|
||||||
switch (aplType) {
|
|
||||||
case "vercel":
|
|
||||||
apl = new VercelAPL();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "upstash":
|
|
||||||
apl = new UpstashAPL();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "file":
|
|
||||||
apl = new FileAPL();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "rest": {
|
|
||||||
if (!process.env.REST_APL_ENDPOINT || !process.env.REST_APL_TOKEN) {
|
|
||||||
throw new Error("Rest APL is not configured - missing env variables. Check saleor-app.ts");
|
|
||||||
}
|
|
||||||
|
|
||||||
apl = new SaleorCloudAPL({
|
|
||||||
resourceUrl: process.env.REST_APL_ENDPOINT,
|
|
||||||
token: process.env.REST_APL_TOKEN,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error("Invalid APL config, ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const saleorApp = new SaleorApp({
|
|
||||||
apl,
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
// This file configures the initialization of Sentry on the browser.
|
|
||||||
// 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";
|
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: SENTRY_DSN,
|
|
||||||
// Adjust this value in production, or use tracesSampler for greater control
|
|
||||||
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
|
|
||||||
// that it will also get attached to your source maps
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
// 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.
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: SENTRY_DSN,
|
|
||||||
// Adjust this value in production, or use tracesSampler for greater control
|
|
||||||
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
|
|
||||||
// that it will also get attached to your source maps
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
// This file configures the initialization of Sentry on the server.
|
|
||||||
// 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";
|
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: SENTRY_DSN,
|
|
||||||
// Adjust this value in production, or use tracesSampler for greater control
|
|
||||||
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
|
|
||||||
// that it will also get attached to your source maps
|
|
||||||
});
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { SellerShopConfig } from "../modules/app-configuration/app-config";
|
|
||||||
|
|
||||||
export const getMockAddress = (): SellerShopConfig["address"] => {
|
|
||||||
return {
|
|
||||||
city: "Wrocław",
|
|
||||||
cityArea: "",
|
|
||||||
companyName: "Saleor",
|
|
||||||
country: "Poland",
|
|
||||||
countryArea: "Dolnoslaskie",
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
postalCode: "12-123",
|
|
||||||
streetAddress1: "Techowa 7",
|
|
||||||
streetAddress2: "",
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,102 +0,0 @@
|
||||||
import { OrderPayloadFragment, OrderStatus } from "../../generated/graphql";
|
|
||||||
|
|
||||||
export const mockOrder: OrderPayloadFragment = {
|
|
||||||
channel: {
|
|
||||||
slug: "default-channel",
|
|
||||||
},
|
|
||||||
shippingPrice: {
|
|
||||||
currency: "USD",
|
|
||||||
gross: {
|
|
||||||
amount: 1,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
net: {
|
|
||||||
amount: 1,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
tax: {
|
|
||||||
amount: 0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shippingMethodName: "CyCreateVariants-1462",
|
|
||||||
number: "3991",
|
|
||||||
id: "T3JkZXI6OTFiZjM5ZDQtZjRiMC00M2QyLTgwMjEtZjVkMTMwNDVlMjkx",
|
|
||||||
billingAddress: {
|
|
||||||
id: "QWRkcmVzczoxNzE4Ng==",
|
|
||||||
country: {
|
|
||||||
country: "Poland",
|
|
||||||
code: "PL",
|
|
||||||
},
|
|
||||||
companyName: "Fajna firma lol",
|
|
||||||
cityArea: "",
|
|
||||||
countryArea: "",
|
|
||||||
streetAddress1: "street 1",
|
|
||||||
streetAddress2: "Street 2",
|
|
||||||
postalCode: "55-123",
|
|
||||||
phone: "+48690563008",
|
|
||||||
firstName: "MAdzia",
|
|
||||||
lastName: "Markusik",
|
|
||||||
city: "WRO",
|
|
||||||
},
|
|
||||||
created: "2022-12-02T15:05:56.637068+00:00",
|
|
||||||
fulfillments: [],
|
|
||||||
status: OrderStatus.Unfulfilled,
|
|
||||||
total: {
|
|
||||||
currency: "USD",
|
|
||||||
gross: {
|
|
||||||
amount: 207.15,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
net: {
|
|
||||||
amount: 206,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
tax: {
|
|
||||||
amount: 1.15,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lines: [
|
|
||||||
{
|
|
||||||
productName: "Tales of pirate kittycat",
|
|
||||||
variantName: "Signed: Yes / vinyl",
|
|
||||||
quantity: 2,
|
|
||||||
totalPrice: {
|
|
||||||
currency: "USD",
|
|
||||||
gross: {
|
|
||||||
amount: 200,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
net: {
|
|
||||||
amount: 200,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
tax: {
|
|
||||||
amount: 0,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
productName: "White Hoodie",
|
|
||||||
variantName: "10404946",
|
|
||||||
quantity: 1,
|
|
||||||
totalPrice: {
|
|
||||||
currency: "USD",
|
|
||||||
gross: {
|
|
||||||
amount: 6.15,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
net: {
|
|
||||||
amount: 5,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
tax: {
|
|
||||||
amount: 1.15,
|
|
||||||
currency: "USD",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { AuthConfig, authExchange } from "@urql/exchange-auth";
|
|
||||||
import { cacheExchange, createClient as urqlCreateClient, dedupExchange } from "urql";
|
|
||||||
|
|
||||||
import { multipartFetchExchange } from "@urql/exchange-multipart-fetch";
|
|
||||||
|
|
||||||
interface IAuthState {
|
|
||||||
token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createClient = (url: string, getAuth: AuthConfig<IAuthState>["getAuth"]) =>
|
|
||||||
urqlCreateClient({
|
|
||||||
url,
|
|
||||||
exchanges: [
|
|
||||||
dedupExchange,
|
|
||||||
cacheExchange,
|
|
||||||
authExchange<IAuthState>({
|
|
||||||
addAuthToOperation: ({ authState, operation }) => {
|
|
||||||
if (!authState || !authState?.token) {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchOptions =
|
|
||||||
typeof operation.context.fetchOptions === "function"
|
|
||||||
? operation.context.fetchOptions()
|
|
||||||
: operation.context.fetchOptions || {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...operation,
|
|
||||||
context: {
|
|
||||||
...operation.context,
|
|
||||||
fetchOptions: {
|
|
||||||
...fetchOptions,
|
|
||||||
headers: {
|
|
||||||
...fetchOptions.headers,
|
|
||||||
"Authorization-Bearer": authState.token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getAuth,
|
|
||||||
}),
|
|
||||||
multipartFetchExchange,
|
|
||||||
],
|
|
||||||
});
|
|
|
@ -1,19 +0,0 @@
|
||||||
import pino from "pino";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO Set up log drain etc
|
|
||||||
*/
|
|
||||||
export const logger = pino({
|
|
||||||
level: process.env.APP_DEBUG ?? "silent",
|
|
||||||
transport:
|
|
||||||
process.env.NODE_ENV === "development"
|
|
||||||
? {
|
|
||||||
target: "pino-pretty",
|
|
||||||
options: {
|
|
||||||
colorize: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createLogger = logger.child.bind(logger);
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { SellerShopConfig } from "./app-config";
|
|
||||||
|
|
||||||
export const Address = {
|
|
||||||
createEmpty(): SellerShopConfig["address"] {
|
|
||||||
return {
|
|
||||||
city: "",
|
|
||||||
cityArea: "",
|
|
||||||
companyName: "",
|
|
||||||
country: "",
|
|
||||||
countryArea: "",
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
postalCode: "",
|
|
||||||
streetAddress1: "",
|
|
||||||
streetAddress2: "",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { describe, it, expect } from "vitest";
|
|
||||||
import { AppConfigContainer } from "./app-config-container";
|
|
||||||
import { AppConfig, SellerShopConfig } from "./app-config";
|
|
||||||
|
|
||||||
const getDefaultAddressData = (): SellerShopConfig["address"] => ({
|
|
||||||
city: "",
|
|
||||||
cityArea: "",
|
|
||||||
companyName: "Saleor",
|
|
||||||
country: "",
|
|
||||||
countryArea: "",
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
postalCode: "",
|
|
||||||
streetAddress1: "",
|
|
||||||
streetAddress2: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AppConfigContainer", () => {
|
|
||||||
describe("Get address from config", () => {
|
|
||||||
it("Gets address if exists", () => {
|
|
||||||
expect(
|
|
||||||
AppConfigContainer.getChannelAddress({
|
|
||||||
shopConfigPerChannel: {
|
|
||||||
channel: {
|
|
||||||
address: getDefaultAddressData(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})("channel")
|
|
||||||
).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
companyName: "Saleor",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Returns null if entire config is null", () => {
|
|
||||||
expect(AppConfigContainer.getChannelAddress(null)("channel")).toEqual(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Set address to config per slug of the channel", () => {
|
|
||||||
it("Will create entire config object if initially was null", () => {
|
|
||||||
const newConfig = AppConfigContainer.setChannelAddress(null)("channel")(
|
|
||||||
getDefaultAddressData()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(newConfig).toEqual({
|
|
||||||
shopConfigPerChannel: expect.objectContaining({
|
|
||||||
channel: expect.objectContaining({
|
|
||||||
address: expect.objectContaining({ companyName: "Saleor" }),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Will preserve another existing config for another channel after setting a new one", () => {
|
|
||||||
const config: AppConfig = {
|
|
||||||
shopConfigPerChannel: {
|
|
||||||
c1: {
|
|
||||||
address: {
|
|
||||||
...getDefaultAddressData(),
|
|
||||||
companyName: "Mirumee",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const newConfig = AppConfigContainer.setChannelAddress(config)("c2")(getDefaultAddressData());
|
|
||||||
|
|
||||||
expect(newConfig.shopConfigPerChannel.c1.address.companyName).toEqual("Mirumee");
|
|
||||||
expect(newConfig.shopConfigPerChannel.c2.address.companyName).toEqual("Saleor");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { AppConfig, SellerShopConfig } from "./app-config";
|
|
||||||
|
|
||||||
const getDefaultEmptyAddress = (): SellerShopConfig["address"] => ({
|
|
||||||
city: "",
|
|
||||||
cityArea: "",
|
|
||||||
companyName: "",
|
|
||||||
country: "",
|
|
||||||
countryArea: "",
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
postalCode: "",
|
|
||||||
streetAddress1: "",
|
|
||||||
streetAddress2: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const getChannelAddress = (appConfig: AppConfig | null | undefined) => (channelSlug: string) => {
|
|
||||||
try {
|
|
||||||
return appConfig?.shopConfigPerChannel[channelSlug].address ?? null;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setChannelAddress =
|
|
||||||
(appConfig: AppConfig | null | undefined) =>
|
|
||||||
(channelSlug: string) =>
|
|
||||||
(address: SellerShopConfig["address"]) => {
|
|
||||||
const appConfigNormalized = structuredClone(appConfig) ?? { shopConfigPerChannel: {} };
|
|
||||||
|
|
||||||
appConfigNormalized.shopConfigPerChannel[channelSlug] ??= { address: getDefaultEmptyAddress() };
|
|
||||||
appConfigNormalized.shopConfigPerChannel[channelSlug].address = address;
|
|
||||||
|
|
||||||
return appConfigNormalized;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AppConfigContainer = {
|
|
||||||
getChannelAddress,
|
|
||||||
setChannelAddress,
|
|
||||||
};
|
|
|
@ -1,49 +0,0 @@
|
||||||
import {describe, it, expect} from "vitest";
|
|
||||||
import {appConfigInputSchema} from "./app-config-input-schema";
|
|
||||||
import {AppConfig, SellerShopConfig} from "./app-config";
|
|
||||||
import {getMockAddress} from "../../fixtures/mock-address";
|
|
||||||
|
|
||||||
describe("appConfigInputSchema", () => {
|
|
||||||
it('Passes with no channels at all', () => {
|
|
||||||
expect(() =>
|
|
||||||
appConfigInputSchema.parse({
|
|
||||||
shopConfigPerChannel: {}
|
|
||||||
} satisfies AppConfig)
|
|
||||||
).not.to.throw()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Passes with all address fields empty', () => {
|
|
||||||
expect(() =>
|
|
||||||
appConfigInputSchema.parse({
|
|
||||||
shopConfigPerChannel: {
|
|
||||||
channel: {
|
|
||||||
address: {
|
|
||||||
city: "",
|
|
||||||
cityArea: "",
|
|
||||||
companyName: "",
|
|
||||||
country: "",
|
|
||||||
countryArea: "",
|
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
postalCode: "",
|
|
||||||
streetAddress1: "",
|
|
||||||
streetAddress2: ""
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} satisfies AppConfig)
|
|
||||||
).not.to.throw()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Passes with partial address', () => {
|
|
||||||
expect(() =>
|
|
||||||
appConfigInputSchema.parse({
|
|
||||||
shopConfigPerChannel: {
|
|
||||||
channel: {
|
|
||||||
address: getMockAddress() }
|
|
||||||
}
|
|
||||||
} satisfies AppConfig)
|
|
||||||
).not.to.throw()
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const appConfigInputSchema = z.object({
|
|
||||||
shopConfigPerChannel: z.record(
|
|
||||||
z.object({
|
|
||||||
address: z.object({
|
|
||||||
/**
|
|
||||||
* min() to allow empty strings
|
|
||||||
*/
|
|
||||||
companyName: z.string().min(0),
|
|
||||||
cityArea: z.string().min(0),
|
|
||||||
countryArea: z.string().min(0),
|
|
||||||
streetAddress1: z.string().min(0),
|
|
||||||
streetAddress2: z.string().min(0),
|
|
||||||
postalCode: z.string().min(0),
|
|
||||||
firstName: z.string().min(0),
|
|
||||||
lastName: z.string().min(0),
|
|
||||||
city: z.string().min(0),
|
|
||||||
country: z.string().min(0),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { AddressFragment } from "../../../generated/graphql";
|
|
||||||
|
|
||||||
export interface SellerShopConfig {
|
|
||||||
address: {
|
|
||||||
companyName: string;
|
|
||||||
cityArea: string;
|
|
||||||
countryArea: string;
|
|
||||||
streetAddress1: string;
|
|
||||||
streetAddress2: string;
|
|
||||||
postalCode: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
city: string;
|
|
||||||
country: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ShopConfigPerChannelSlug = Record<string, SellerShopConfig>;
|
|
||||||
|
|
||||||
export type AppConfig = {
|
|
||||||
shopConfigPerChannel: ShopConfigPerChannelSlug;
|
|
||||||
};
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { router } from "../trpc/trpc-server";
|
|
||||||
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
|
||||||
import { PrivateMetadataAppConfigurator } from "./app-configurator";
|
|
||||||
import { createSettingsManager } from "./metadata-manager";
|
|
||||||
import { logger as pinoLogger } from "../../lib/logger";
|
|
||||||
import { appConfigInputSchema } from "./app-config-input-schema";
|
|
||||||
import { GetAppConfigurationService } from "./get-app-configuration.service";
|
|
||||||
|
|
||||||
export const appConfigurationRouter = router({
|
|
||||||
fetch: protectedClientProcedure.query(async ({ ctx, input }) => {
|
|
||||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
|
||||||
|
|
||||||
logger.debug("appConfigurationRouter.fetch called");
|
|
||||||
|
|
||||||
return new GetAppConfigurationService({
|
|
||||||
apiClient: ctx.apiClient,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
}).getConfiguration();
|
|
||||||
}),
|
|
||||||
setAndReplace: protectedClientProcedure
|
|
||||||
.input(appConfigInputSchema)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
|
||||||
|
|
||||||
logger.info(input, "appConfigurationRouter.setAndReplace called with input");
|
|
||||||
|
|
||||||
const appConfigurator = new PrivateMetadataAppConfigurator(
|
|
||||||
createSettingsManager(ctx.apiClient),
|
|
||||||
ctx.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
await appConfigurator.setConfig(input);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
});
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { AppConfig } from "./app-config";
|
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
|
|
||||||
export interface AppConfigurator {
|
|
||||||
setConfig(config: AppConfig): Promise<void>;
|
|
||||||
getConfig(): Promise<AppConfig | undefined>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PrivateMetadataAppConfigurator implements AppConfigurator {
|
|
||||||
private metadataKey = "app-config";
|
|
||||||
|
|
||||||
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
|
|
||||||
|
|
||||||
getConfig(): Promise<AppConfig | undefined> {
|
|
||||||
return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => {
|
|
||||||
if (!data) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(data);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error("Invalid metadata value, cant be parsed");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(config: AppConfig): Promise<void> {
|
|
||||||
return this.metadataManager.set({
|
|
||||||
key: this.metadataKey,
|
|
||||||
value: JSON.stringify(config),
|
|
||||||
domain: this.saleorApiUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { AppConfig } from "./app-config";
|
|
||||||
import { AppConfigContainer } from "./app-config-container";
|
|
||||||
import { ChannelFragment, ShopInfoFragment } from "../../../generated/graphql";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO Test
|
|
||||||
*/
|
|
||||||
export const FallbackAppConfig = {
|
|
||||||
createFallbackConfigFromExistingShopAndChannels(
|
|
||||||
channels: ChannelFragment[],
|
|
||||||
shopAddress: ShopInfoFragment | null
|
|
||||||
) {
|
|
||||||
return (channels ?? []).reduce<AppConfig>(
|
|
||||||
(state, channel) => {
|
|
||||||
return AppConfigContainer.setChannelAddress(state)(channel.slug)({
|
|
||||||
city: shopAddress?.companyAddress?.city ?? "",
|
|
||||||
cityArea: shopAddress?.companyAddress?.cityArea ?? "",
|
|
||||||
companyName: shopAddress?.companyAddress?.companyName ?? "",
|
|
||||||
country: shopAddress?.companyAddress?.country.country ?? "",
|
|
||||||
countryArea: shopAddress?.companyAddress?.countryArea ?? "",
|
|
||||||
firstName: shopAddress?.companyAddress?.firstName ?? "",
|
|
||||||
lastName: shopAddress?.companyAddress?.lastName ?? "",
|
|
||||||
postalCode: shopAddress?.companyAddress?.postalCode ?? "",
|
|
||||||
streetAddress1: shopAddress?.companyAddress?.streetAddress1 ?? "",
|
|
||||||
streetAddress2: shopAddress?.companyAddress?.streetAddress2 ?? "",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ shopConfigPerChannel: {} }
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,65 +0,0 @@
|
||||||
import { PrivateMetadataAppConfigurator } from "./app-configurator";
|
|
||||||
import { createSettingsManager } from "./metadata-manager";
|
|
||||||
import { ChannelsFetcher } from "../channels/channels-fetcher";
|
|
||||||
import { ShopInfoFetcher } from "../shop-info/shop-info-fetcher";
|
|
||||||
import { FallbackAppConfig } from "./fallback-app-config";
|
|
||||||
import { Client } from "urql";
|
|
||||||
import { logger as pinoLogger } from "../../lib/logger";
|
|
||||||
|
|
||||||
// todo test
|
|
||||||
export class GetAppConfigurationService {
|
|
||||||
constructor(
|
|
||||||
private settings: {
|
|
||||||
apiClient: Client;
|
|
||||||
saleorApiUrl: string;
|
|
||||||
}
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getConfiguration() {
|
|
||||||
const logger = pinoLogger.child({
|
|
||||||
service: "GetAppConfigurationService",
|
|
||||||
saleorApiUrl: this.settings.saleorApiUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { saleorApiUrl, apiClient } = this.settings;
|
|
||||||
|
|
||||||
const appConfigurator = new PrivateMetadataAppConfigurator(
|
|
||||||
createSettingsManager(apiClient),
|
|
||||||
saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const savedAppConfig = (await appConfigurator.getConfig()) ?? null;
|
|
||||||
|
|
||||||
logger.debug(savedAppConfig, "Retrieved app config from Metadata. Will return it");
|
|
||||||
|
|
||||||
if (savedAppConfig) {
|
|
||||||
return savedAppConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("App config not found in metadata. Will create default config now.");
|
|
||||||
|
|
||||||
const channelsFetcher = new ChannelsFetcher(apiClient);
|
|
||||||
const shopInfoFetcher = new ShopInfoFetcher(apiClient);
|
|
||||||
|
|
||||||
const [channels, shopAddress] = await Promise.all([
|
|
||||||
channelsFetcher.fetchChannels(),
|
|
||||||
shopInfoFetcher.fetchShopInfo(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
logger.debug(channels, "Fetched channels");
|
|
||||||
logger.debug(shopAddress, "Fetched shop address");
|
|
||||||
|
|
||||||
const appConfig = FallbackAppConfig.createFallbackConfigFromExistingShopAndChannels(
|
|
||||||
channels ?? [],
|
|
||||||
shopAddress
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug(appConfig, "Created a fallback AppConfig. Will save it.");
|
|
||||||
|
|
||||||
await appConfigurator.setConfig(appConfig);
|
|
||||||
|
|
||||||
logger.info("Saved initial AppConfig");
|
|
||||||
|
|
||||||
return appConfig;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
import { MetadataEntry, EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { Client, gql } from "urql";
|
|
||||||
import {
|
|
||||||
FetchAppDetailsDocument,
|
|
||||||
FetchAppDetailsQuery,
|
|
||||||
UpdateAppMetadataDocument,
|
|
||||||
} from "../../../generated/graphql";
|
|
||||||
|
|
||||||
gql`
|
|
||||||
mutation UpdateAppMetadata($id: ID!, $input: [MetadataInput!]!) {
|
|
||||||
updatePrivateMetadata(id: $id, input: $input) {
|
|
||||||
item {
|
|
||||||
privateMetadata {
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
gql`
|
|
||||||
query FetchAppDetails {
|
|
||||||
app {
|
|
||||||
id
|
|
||||||
privateMetadata {
|
|
||||||
key
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export async function fetchAllMetadata(client: Client): Promise<MetadataEntry[]> {
|
|
||||||
const { error, data } = await client
|
|
||||||
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) {
|
|
||||||
// to update the metadata, ID is required
|
|
||||||
const { error: idQueryError, data: idQueryData } = await client
|
|
||||||
.query(FetchAppDetailsDocument, {})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (idQueryError) {
|
|
||||||
throw new Error(
|
|
||||||
"Could not fetch the app id. Please check if auth data for the client are valid."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const appId = idQueryData?.app?.id;
|
|
||||||
|
|
||||||
if (!appId) {
|
|
||||||
throw new Error("Could not fetch the app ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { error: mutationError, data: mutationData } = await client
|
|
||||||
.mutation(UpdateAppMetadataDocument, {
|
|
||||||
id: appId,
|
|
||||||
input: metadata,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (mutationError) {
|
|
||||||
throw new Error(`Mutation error: ${mutationError.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
mutationData?.updatePrivateMetadata?.item?.privateMetadata.map((md) => ({
|
|
||||||
key: md.key,
|
|
||||||
value: md.value,
|
|
||||||
})) || []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
// If your use case require plain text values, you can use MetadataManager.
|
|
||||||
return new EncryptedMetadataManager({
|
|
||||||
// Secret key should be randomly created for production and set as environment variable
|
|
||||||
encryptionKey: process.env.SECRET_KEY!,
|
|
||||||
fetchMetadata: () => fetchAllMetadata(client),
|
|
||||||
mutateMetadata: (metadata) => mutateMetadata(client, metadata),
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { SellerShopConfig } from "../app-config";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { TextField, TextFieldProps, Typography } from "@material-ui/core";
|
|
||||||
import { Button, makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
|
||||||
field: {
|
|
||||||
marginBottom: 20,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
padding: 20,
|
|
||||||
},
|
|
||||||
channelName: {
|
|
||||||
fontFamily: "monospace",
|
|
||||||
cursor: "pointer",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
channelSlug: string;
|
|
||||||
channelName: string;
|
|
||||||
channelID: string;
|
|
||||||
onSubmit(data: SellerShopConfig["address"]): Promise<void>;
|
|
||||||
initialData?: SellerShopConfig["address"] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AddressForm = (props: Props) => {
|
|
||||||
const { register, handleSubmit } = useForm<SellerShopConfig["address"]>({
|
|
||||||
defaultValues: props.initialData ?? undefined,
|
|
||||||
});
|
|
||||||
const styles = useStyles();
|
|
||||||
const { appBridge } = useAppBridge();
|
|
||||||
|
|
||||||
const CommonFieldProps: TextFieldProps = {
|
|
||||||
className: styles.field,
|
|
||||||
fullWidth: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChannelNameClick = () => {
|
|
||||||
appBridge?.dispatch(
|
|
||||||
actions.Redirect({
|
|
||||||
to: `/channels/${props.channelID}`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmit((data, event) => {
|
|
||||||
props.onSubmit(data);
|
|
||||||
})}
|
|
||||||
className={styles.form}
|
|
||||||
>
|
|
||||||
<Typography variant="body1" paragraph>
|
|
||||||
Configure
|
|
||||||
<strong onClick={handleChannelNameClick} className={styles.channelName}>
|
|
||||||
{` ${props.channelName} `}
|
|
||||||
</strong>
|
|
||||||
channel:
|
|
||||||
</Typography>
|
|
||||||
<TextField label="Company Name" {...CommonFieldProps} {...register("companyName")} />
|
|
||||||
<TextField {...CommonFieldProps} label="First Name" {...register("firstName")} />
|
|
||||||
<TextField {...CommonFieldProps} label="Last Name" {...register("lastName")} />
|
|
||||||
<TextField label="Street Address 1" {...CommonFieldProps} {...register("streetAddress1")} />
|
|
||||||
<TextField {...CommonFieldProps} label="Street Address 2" {...register("streetAddress2")} />
|
|
||||||
<div style={{ display: "grid", gap: 20, gridTemplateColumns: "1fr 2fr" }}>
|
|
||||||
<TextField {...CommonFieldProps} label="Postal Code" {...register("postalCode")} />
|
|
||||||
<TextField {...CommonFieldProps} label="City" {...register("city")} />
|
|
||||||
</div>
|
|
||||||
<TextField {...CommonFieldProps} label="City Area" {...register("cityArea")} />
|
|
||||||
<TextField {...CommonFieldProps} label="Country" {...register("country")} />
|
|
||||||
<TextField label="Country Area" {...CommonFieldProps} {...register("countryArea")} />
|
|
||||||
<Button type="submit" fullWidth variant="primary">
|
|
||||||
Save channel configuration
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,103 +0,0 @@
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
|
||||||
import { LinearProgress, Paper, Typography } from "@material-ui/core";
|
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import { AppConfigContainer } from "../app-config-container";
|
|
||||||
import { AddressForm } from "./address-form";
|
|
||||||
import { ChannelsList } from "./channels-list";
|
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => {
|
|
||||||
return {
|
|
||||||
header: { marginBottom: 20 },
|
|
||||||
grid: { display: "grid", gridTemplateColumns: "1fr 1fr", alignItems: "start", gap: 40 },
|
|
||||||
formContainer: {
|
|
||||||
top: 0,
|
|
||||||
position: "sticky",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ChannelsConfiguration = () => {
|
|
||||||
const styles = useStyles();
|
|
||||||
const { appBridge } = useAppBridge();
|
|
||||||
|
|
||||||
const { data: configurationData, refetch: refetchConfig } =
|
|
||||||
trpcClient.appConfiguration.fetch.useQuery();
|
|
||||||
|
|
||||||
const channels = trpcClient.channels.fetch.useQuery();
|
|
||||||
|
|
||||||
const [activeChannelSlug, setActiveChannelSlug] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const { mutate, error: saveError } = trpcClient.appConfiguration.setAndReplace.useMutation({
|
|
||||||
onSuccess() {
|
|
||||||
refetchConfig();
|
|
||||||
appBridge?.dispatch(
|
|
||||||
actions.Notification({
|
|
||||||
title: "Success",
|
|
||||||
text: "Saved app configuration",
|
|
||||||
status: "success",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (channels.isSuccess) {
|
|
||||||
setActiveChannelSlug(channels.data![0].slug ?? null);
|
|
||||||
}
|
|
||||||
}, [channels.isSuccess, channels.data]);
|
|
||||||
|
|
||||||
const activeChannel = useMemo(() => {
|
|
||||||
try {
|
|
||||||
return channels.data!.find((c) => c.slug === activeChannelSlug)!;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [channels.data, activeChannelSlug]);
|
|
||||||
|
|
||||||
if (channels.isLoading || !channels.data) {
|
|
||||||
return <LinearProgress />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeChannel) {
|
|
||||||
return <div>Error. No channel available</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Typography className={styles.header} variant="subtitle1">
|
|
||||||
Configure seller details visible on the invoice
|
|
||||||
</Typography>
|
|
||||||
<div className={styles.grid}>
|
|
||||||
<ChannelsList
|
|
||||||
channels={channels.data}
|
|
||||||
activeChannelSlug={activeChannel.slug}
|
|
||||||
onChannelClick={setActiveChannelSlug}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{activeChannel && (
|
|
||||||
<Paper elevation={0} className={styles.formContainer}>
|
|
||||||
<AddressForm
|
|
||||||
channelID={activeChannel.id}
|
|
||||||
key={activeChannelSlug}
|
|
||||||
channelSlug={activeChannel.slug}
|
|
||||||
onSubmit={async (data) => {
|
|
||||||
const newConfig = AppConfigContainer.setChannelAddress(configurationData)(
|
|
||||||
activeChannel.slug
|
|
||||||
)(data);
|
|
||||||
|
|
||||||
mutate(newConfig);
|
|
||||||
}}
|
|
||||||
initialData={AppConfigContainer.getChannelAddress(configurationData)(
|
|
||||||
activeChannel.slug
|
|
||||||
)}
|
|
||||||
channelName={activeChannel?.name ?? activeChannelSlug}
|
|
||||||
/>
|
|
||||||
{saveError && <span>{saveError.message}</span>}
|
|
||||||
</Paper>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,71 +0,0 @@
|
||||||
import {
|
|
||||||
makeStyles,
|
|
||||||
OffsettedList,
|
|
||||||
OffsettedListBody,
|
|
||||||
OffsettedListHeader,
|
|
||||||
OffsettedListItem,
|
|
||||||
OffsettedListItemCell,
|
|
||||||
} from "@saleor/macaw-ui";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import React from "react";
|
|
||||||
import { ChannelFragment } from "../../../../generated/graphql";
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => {
|
|
||||||
return {
|
|
||||||
listItem: {
|
|
||||||
cursor: "pointer",
|
|
||||||
height: "auto !important",
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "1fr 1fr",
|
|
||||||
},
|
|
||||||
listItemActive: {
|
|
||||||
border: `2px solid ${theme.palette.primary.main}`,
|
|
||||||
},
|
|
||||||
cellSlug: {
|
|
||||||
fontFamily: "monospace",
|
|
||||||
opacity: 0.8,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
channels: ChannelFragment[];
|
|
||||||
activeChannelSlug: string;
|
|
||||||
onChannelClick(channelSlug: string): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChannelsList = ({ channels, activeChannelSlug, onChannelClick }: Props) => {
|
|
||||||
const styles = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OffsettedList gridTemplate={["1fr", "1fr"]}>
|
|
||||||
<OffsettedListHeader>
|
|
||||||
<OffsettedListItem className={styles.listItem}>
|
|
||||||
<OffsettedListItemCell>Channel name</OffsettedListItemCell>
|
|
||||||
<OffsettedListItemCell>Channel slug</OffsettedListItemCell>
|
|
||||||
</OffsettedListItem>
|
|
||||||
</OffsettedListHeader>
|
|
||||||
<OffsettedListBody>
|
|
||||||
{channels.map((c) => {
|
|
||||||
return (
|
|
||||||
<OffsettedListItem
|
|
||||||
className={clsx(styles.listItem, {
|
|
||||||
[styles.listItemActive]: c.slug === activeChannelSlug,
|
|
||||||
})}
|
|
||||||
key={c.slug}
|
|
||||||
onClick={() => {
|
|
||||||
onChannelClick(c.slug);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<OffsettedListItemCell>{c.name}</OffsettedListItemCell>
|
|
||||||
<OffsettedListItemCell className={styles.cellSlug}>
|
|
||||||
<Typography variant="caption">{c.slug}</Typography>
|
|
||||||
</OffsettedListItemCell>
|
|
||||||
</OffsettedListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</OffsettedListBody>
|
|
||||||
</OffsettedList>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { Client, gql } from "urql";
|
|
||||||
import { FetchChannelsDocument } from "../../../generated/graphql";
|
|
||||||
|
|
||||||
gql`
|
|
||||||
fragment Channel on Channel {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
|
|
||||||
query FetchChannels {
|
|
||||||
channels {
|
|
||||||
...Channel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class ChannelsFetcher {
|
|
||||||
constructor(private client: Client) {}
|
|
||||||
|
|
||||||
fetchChannels() {
|
|
||||||
return this.client
|
|
||||||
.query(FetchChannelsDocument, {})
|
|
||||||
.toPromise()
|
|
||||||
.then((r) => r.data?.channels ?? null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { createClient } from "../../lib/graphql";
|
|
||||||
import { router } from "../trpc/trpc-server";
|
|
||||||
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
|
||||||
import { ChannelsFetcher } from "./channels-fetcher";
|
|
||||||
import { ChannelFragment } from "../../../generated/graphql";
|
|
||||||
|
|
||||||
export const channelsRouter = router({
|
|
||||||
fetch: protectedClientProcedure.query(async ({ ctx, input }): Promise<ChannelFragment[]> => {
|
|
||||||
const client = createClient(ctx.saleorApiUrl, async () =>
|
|
||||||
Promise.resolve({ token: ctx.appToken })
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetcher = new ChannelsFetcher(client);
|
|
||||||
|
|
||||||
return fetcher.fetchChannels().then((channels) => channels ?? []);
|
|
||||||
}),
|
|
||||||
});
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { Client, gql } from "urql";
|
|
||||||
import { InvoiceCreateDocument } from "../../../generated/graphql";
|
|
||||||
import { logger } from "../../lib/logger";
|
|
||||||
|
|
||||||
gql`
|
|
||||||
mutation InvoiceCreate($orderId: ID!, $invoiceInput: InvoiceCreateInput!) {
|
|
||||||
invoiceCreate(input: $invoiceInput, orderId: $orderId) {
|
|
||||||
errors {
|
|
||||||
message
|
|
||||||
}
|
|
||||||
invoice {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class InvoiceCreateNotifier {
|
|
||||||
constructor(private client: Client) {}
|
|
||||||
|
|
||||||
notifyInvoiceCreated(orderId: string, invoiceNumber: string, invoiceUrl: string) {
|
|
||||||
logger.info(
|
|
||||||
{ orderId, invoiceNumber, invoiceUrl },
|
|
||||||
"Will notify Saleor with invoiceCreate mutation"
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.client
|
|
||||||
.mutation(InvoiceCreateDocument, {
|
|
||||||
orderId,
|
|
||||||
invoiceInput: {
|
|
||||||
url: invoiceUrl,
|
|
||||||
number: invoiceNumber,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.toPromise()
|
|
||||||
.then((result) => {
|
|
||||||
logger.info(result.data, "invoiceCreate finished");
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
throw new Error(result.error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { describe, it, expect, vi } from "vitest";
|
|
||||||
import { hashInvoiceFilename } from "./hash-invoice-filename";
|
|
||||||
|
|
||||||
vi.mock("crypto", () => ({
|
|
||||||
randomUUID() {
|
|
||||||
return "RANDOM_UUID_MOCK";
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("hashInvoiceFilename", () => {
|
|
||||||
it("Creates hashed invoice name", () => {
|
|
||||||
expect(hashInvoiceFilename("1/12/2022", "1234-xxxx-zzzz-1234")).toBe(
|
|
||||||
"1/12/2022_1234-xxxx-zzzz-1234_RANDOM_UUID_MOCK"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { randomUUID } from "crypto";
|
|
||||||
|
|
||||||
export const hashInvoiceFilename = (invoiceName: string, orderId: string) => {
|
|
||||||
return `${invoiceName}_${orderId}_${randomUUID()}`;
|
|
||||||
};
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import { resolveTempPdfFileLocation } from "./resolve-temp-pdf-file-location";
|
|
||||||
|
|
||||||
describe("resolveTempPdfFileLocation", () => {
|
|
||||||
it("generates path with encoded file name, in case of invoice name contains path segments", () => {
|
|
||||||
expect(resolveTempPdfFileLocation("12/12/2022-foobar.pdf")).toBe("12%2F12%2F2022-foobar.pdf");
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { join } from "path";
|
|
||||||
import invariant from "tiny-invariant";
|
|
||||||
|
|
||||||
export const resolveTempPdfFileLocation = (fileName: string) => {
|
|
||||||
invariant(fileName.includes(".pdf"), `fileName should include pdf extension`);
|
|
||||||
|
|
||||||
return join(process.env.TEMP_PDF_STORAGE_DIR ?? "", encodeURIComponent(fileName));
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { OrderPayloadFragment } from "../../../generated/graphql";
|
|
||||||
import { SellerShopConfig } from "../app-configuration/app-config";
|
|
||||||
|
|
||||||
export interface InvoiceGenerator {
|
|
||||||
generate(input: {
|
|
||||||
order: OrderPayloadFragment;
|
|
||||||
invoiceNumber: string;
|
|
||||||
filename: string;
|
|
||||||
companyAddressData: SellerShopConfig["address"];
|
|
||||||
}): Promise<void>;
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { beforeEach, describe, it } from "vitest";
|
|
||||||
import { MicroinvoiceInvoiceGenerator } from "./microinvoice-invoice-generator";
|
|
||||||
import { readFile } from "fs/promises";
|
|
||||||
import rimraf from "rimraf";
|
|
||||||
import { mockOrder } from "../../../fixtures/mock-order";
|
|
||||||
import { getMockAddress } from "../../../fixtures/mock-address";
|
|
||||||
|
|
||||||
const cleanup = () => rimraf.sync("test-invoice.pdf");
|
|
||||||
|
|
||||||
describe("MicroinvoiceInvoiceGenerator", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
// afterEach(() => {
|
|
||||||
// cleanup();
|
|
||||||
// });
|
|
||||||
|
|
||||||
it("Generates invoice file from Order", async () => {
|
|
||||||
const instance = new MicroinvoiceInvoiceGenerator();
|
|
||||||
|
|
||||||
await instance.generate({
|
|
||||||
order: mockOrder,
|
|
||||||
filename: "test-invoice.pdf",
|
|
||||||
invoiceNumber: "test-123/123",
|
|
||||||
companyAddressData: getMockAddress(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return readFile("test-invoice.pdf");
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,168 +0,0 @@
|
||||||
import { InvoiceGenerator } from "../invoice-generator";
|
|
||||||
import { Order, OrderPayloadFragment } from "../../../../generated/graphql";
|
|
||||||
import { SellerShopConfig } from "../../app-configuration/app-config";
|
|
||||||
const Microinvoice = require("microinvoice");
|
|
||||||
|
|
||||||
export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
|
|
||||||
constructor(
|
|
||||||
private settings = {
|
|
||||||
locale: "en-US",
|
|
||||||
}
|
|
||||||
) {}
|
|
||||||
async generate(input: {
|
|
||||||
order: OrderPayloadFragment;
|
|
||||||
invoiceNumber: string;
|
|
||||||
filename: string;
|
|
||||||
companyAddressData: SellerShopConfig["address"];
|
|
||||||
}): Promise<void> {
|
|
||||||
const { invoiceNumber, order, companyAddressData, filename } = input;
|
|
||||||
|
|
||||||
const microinvoiceInstance = new Microinvoice({
|
|
||||||
style: {
|
|
||||||
// header: {
|
|
||||||
// image: {
|
|
||||||
// path: "./examples/logo.png",
|
|
||||||
// width: 50,
|
|
||||||
// height: 19,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
invoice: {
|
|
||||||
name: `Invoice ${invoiceNumber}`,
|
|
||||||
|
|
||||||
header: [
|
|
||||||
{
|
|
||||||
label: "Order number",
|
|
||||||
value: order.number,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Date",
|
|
||||||
value: Intl.DateTimeFormat(this.settings.locale, {
|
|
||||||
dateStyle: "medium",
|
|
||||||
timeStyle: "medium",
|
|
||||||
}).format(new Date(order.created)),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
currency: order.total.currency,
|
|
||||||
|
|
||||||
customer: [
|
|
||||||
{
|
|
||||||
label: "Customer",
|
|
||||||
value: [
|
|
||||||
`${order.billingAddress?.firstName} ${order.billingAddress?.lastName}`,
|
|
||||||
order.billingAddress?.companyName,
|
|
||||||
order.billingAddress?.phone,
|
|
||||||
`${order.billingAddress?.streetAddress1}`,
|
|
||||||
`${order.billingAddress?.streetAddress2}`,
|
|
||||||
`${order.billingAddress?.postalCode} ${order.billingAddress?.city}`,
|
|
||||||
order.billingAddress?.country.country,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: "Tax Identifier",
|
|
||||||
// value: "todo",
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
seller: [
|
|
||||||
{
|
|
||||||
label: "Seller",
|
|
||||||
value: [
|
|
||||||
`${companyAddressData.firstName} ${companyAddressData.lastName}`,
|
|
||||||
companyAddressData.companyName,
|
|
||||||
companyAddressData.streetAddress1,
|
|
||||||
companyAddressData.streetAddress2,
|
|
||||||
`${companyAddressData.postalCode} ${companyAddressData.city}`,
|
|
||||||
companyAddressData.cityArea,
|
|
||||||
companyAddressData.country,
|
|
||||||
companyAddressData.countryArea,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: "Tax Identifier",
|
|
||||||
// value: "todo",
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
legal: [
|
|
||||||
// {
|
|
||||||
// value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
|
||||||
// weight: "bold",
|
|
||||||
// color: "primary",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: "sed do eiusmod tempor incididunt ut labore et dolore magna.",
|
|
||||||
// weight: "bold",
|
|
||||||
// color: "secondary",
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
details: {
|
|
||||||
header: [
|
|
||||||
{
|
|
||||||
value: "Description",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "Quantity",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "Subtotal",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
parts: [
|
|
||||||
...order.lines.map((line) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: line.productName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: line.quantity,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: line.totalPrice.gross.amount,
|
|
||||||
price: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
value: order.shippingMethodName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "-",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: order.shippingPrice.gross.amount,
|
|
||||||
price: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
total: [
|
|
||||||
{
|
|
||||||
label: "Total net",
|
|
||||||
value: order.total.net.amount,
|
|
||||||
price: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Tax value",
|
|
||||||
value: order.total.tax.amount,
|
|
||||||
price: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Total with tax",
|
|
||||||
value: order.total.gross.amount,
|
|
||||||
price: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return microinvoiceInstance.generate(filename);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { describe, it, expect } from "vitest";
|
|
||||||
import { InvoiceNumberGenerationStrategy } from "./invoice-number-generator";
|
|
||||||
|
|
||||||
describe("InvoiceNumberGenerationStrategies", () => {
|
|
||||||
describe("localizedDate strategy", () => {
|
|
||||||
it("Generates proper name for US locale", () => {
|
|
||||||
const strategy = InvoiceNumberGenerationStrategy.localizedDate("en-US");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Javascript starts counting months from 0
|
|
||||||
*/
|
|
||||||
expect(strategy({ created: new Date(2020, 5, 1).toISOString() })).toBe("6/1/2020");
|
|
||||||
});
|
|
||||||
it("Generates proper name for PL locale", () => {
|
|
||||||
const strategy = InvoiceNumberGenerationStrategy.localizedDate("pl-PL");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Javascript starts counting months from 0
|
|
||||||
*/
|
|
||||||
expect(strategy({ created: new Date(2020, 5, 1).toISOString() })).toBe("1.06.2020");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,19 +0,0 @@
|
||||||
import {OrderPayloadFragment} from "../../../generated/graphql";
|
|
||||||
|
|
||||||
interface IInvoiceNumberGenerationStrategy {
|
|
||||||
(order: OrderPayloadFragment): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const InvoiceNumberGenerationStrategy = {
|
|
||||||
localizedDate: (locale: string) => (order: Pick<OrderPayloadFragment, 'created'>) => {
|
|
||||||
const orderCreatedDate = new Date(order.created);
|
|
||||||
|
|
||||||
return Intl.DateTimeFormat(locale,).format(orderCreatedDate)
|
|
||||||
}
|
|
||||||
} satisfies Record<string, (...args: any[]) => IInvoiceNumberGenerationStrategy>
|
|
||||||
|
|
||||||
export class InvoiceNumberGenerator {
|
|
||||||
generateFromOrder(order: OrderPayloadFragment, strategy: IInvoiceNumberGenerationStrategy): string {
|
|
||||||
return strategy(order)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export interface InvoiceUploader {
|
|
||||||
upload(filePath: string, asName: string): Promise<string>;
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { InvoiceUploader } from "./invoice-uploader";
|
|
||||||
import { Client, gql } from "urql";
|
|
||||||
import { readFile } from "fs/promises";
|
|
||||||
import { FileUploadMutation } from "../../../generated/graphql";
|
|
||||||
/**
|
|
||||||
* Polyfill file because Node doesnt have it yet
|
|
||||||
* https://github.com/nodejs/node/commit/916af4ef2d63fe936a369bcf87ee4f69ec7c67ce
|
|
||||||
*
|
|
||||||
* Use File instead of Blob so Saleor can understand name
|
|
||||||
*/
|
|
||||||
import { File } from "@web-std/file";
|
|
||||||
|
|
||||||
const fileUpload = gql`
|
|
||||||
mutation FileUpload($file: Upload!) {
|
|
||||||
fileUpload(file: $file) {
|
|
||||||
errors {
|
|
||||||
message
|
|
||||||
}
|
|
||||||
uploadedFile {
|
|
||||||
url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class SaleorInvoiceUploader implements InvoiceUploader {
|
|
||||||
constructor(private client: Client) {}
|
|
||||||
|
|
||||||
upload(filePath: string, asName: string): Promise<string> {
|
|
||||||
return readFile(filePath).then((file) => {
|
|
||||||
const blob = new File([file], asName, { type: "application/pdf" });
|
|
||||||
|
|
||||||
return this.client
|
|
||||||
.mutation<FileUploadMutation>(fileUpload, {
|
|
||||||
file: blob,
|
|
||||||
})
|
|
||||||
.toPromise()
|
|
||||||
.then((r) => {
|
|
||||||
if (r.data?.fileUpload?.uploadedFile?.url) {
|
|
||||||
return r.data.fileUpload.uploadedFile.url;
|
|
||||||
} else {
|
|
||||||
throw new Error(r.error?.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { Client, gql } from "urql";
|
|
||||||
import { ShopInfoDocument, ShopInfoFragment } from "../../../generated/graphql";
|
|
||||||
|
|
||||||
gql`
|
|
||||||
fragment ShopInfo on Shop {
|
|
||||||
companyAddress {
|
|
||||||
country {
|
|
||||||
country
|
|
||||||
code
|
|
||||||
}
|
|
||||||
city
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
streetAddress1
|
|
||||||
streetAddress2
|
|
||||||
companyName
|
|
||||||
phone
|
|
||||||
postalCode
|
|
||||||
countryArea
|
|
||||||
cityArea
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query ShopInfo {
|
|
||||||
shop {
|
|
||||||
...ShopInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export interface IShopInfoFetcher {
|
|
||||||
fetchShopInfo(): Promise<ShopInfoFragment | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ShopInfoFetcher implements IShopInfoFetcher {
|
|
||||||
constructor(private client: Client) {}
|
|
||||||
|
|
||||||
fetchShopInfo(): Promise<ShopInfoFragment | null> {
|
|
||||||
return this.client
|
|
||||||
.query(ShopInfoDocument, {})
|
|
||||||
.toPromise()
|
|
||||||
.then((resp) => resp.data?.shop ?? null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
import { createClient } from "../../lib/graphql";
|
|
||||||
import { verifyJWT } from "@saleor/app-sdk/verify-jwt";
|
|
||||||
import { middleware, procedure } from "./trpc-server";
|
|
||||||
import { saleorApp } from "../../../saleor-app";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { ProtectedHandlerError } from "@saleor/app-sdk/handlers/next";
|
|
||||||
import { logger } from "../../lib/logger";
|
|
||||||
|
|
||||||
const attachAppToken = middleware(async ({ ctx, next }) => {
|
|
||||||
logger.debug("attachAppToken middleware");
|
|
||||||
|
|
||||||
if (!ctx.saleorApiUrl) {
|
|
||||||
logger.debug("ctx.saleorApiUrl not found, throwing");
|
|
||||||
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Missing saleorApiUrl in request",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const authData = await saleorApp.apl.get(ctx.saleorApiUrl);
|
|
||||||
|
|
||||||
if (!authData) {
|
|
||||||
logger.debug("authData not found, throwing 401");
|
|
||||||
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Missing auth data",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
appToken: authData.token,
|
|
||||||
saleorApiUrl: authData.saleorApiUrl,
|
|
||||||
appId: authData.appId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const validateClientToken = middleware(async ({ ctx, next }) => {
|
|
||||||
logger.debug("validateClientToken middleware");
|
|
||||||
|
|
||||||
if (!ctx.token) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Missing token in request. This middleware can be used only in frontend",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx.appId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Missing appId in request. This middleware can be used after auth is attached",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx.saleorApiUrl) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message:
|
|
||||||
"Missing saleorApiUrl in request. This middleware can be used after auth is attached",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
logger.debug("trying to verify JWT token from frontend");
|
|
||||||
logger.debug({ token: ctx.token ? `${ctx.token[0]}...` : undefined });
|
|
||||||
|
|
||||||
await verifyJWT({
|
|
||||||
appId: ctx.appId,
|
|
||||||
token: ctx.token,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.debug("JWT verification failed, throwing");
|
|
||||||
throw new ProtectedHandlerError("JWT verification failed: ", "JWT_VERIFICATION_FAILED");
|
|
||||||
}
|
|
||||||
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct common graphQL client and attach it to the context
|
|
||||||
*
|
|
||||||
* Can be used only if called from the frontend (react-query),
|
|
||||||
* otherwise jwks validation will fail (if createCaller used)
|
|
||||||
*
|
|
||||||
* TODO Rethink middleware composition to enable safe server-side router calls
|
|
||||||
*/
|
|
||||||
export const protectedClientProcedure = procedure
|
|
||||||
.use(attachAppToken)
|
|
||||||
.use(validateClientToken)
|
|
||||||
.use(async ({ ctx, next }) => {
|
|
||||||
const client = createClient(ctx.saleorApiUrl, async () =>
|
|
||||||
Promise.resolve({ token: ctx.appToken })
|
|
||||||
);
|
|
||||||
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
apiClient: client,
|
|
||||||
appToken: ctx.appToken,
|
|
||||||
saleorApiUrl: ctx.saleorApiUrl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { channelsRouter } from "../channels/channels.router";
|
|
||||||
import { router } from "./trpc-server";
|
|
||||||
import { appConfigurationRouter } from "../app-configuration/app-configuration.router";
|
|
||||||
|
|
||||||
export const appRouter = router({
|
|
||||||
channels: channelsRouter,
|
|
||||||
appConfiguration: appConfigurationRouter,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { httpBatchLink } from "@trpc/client";
|
|
||||||
import { createTRPCNext } from "@trpc/next";
|
|
||||||
|
|
||||||
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
|
|
||||||
import { appBridgeInstance } from "../../pages/_app";
|
|
||||||
import { AppRouter } from "./trpc-app-router";
|
|
||||||
|
|
||||||
function getBaseUrl() {
|
|
||||||
if (typeof window !== "undefined") return "";
|
|
||||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
|
||||||
|
|
||||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const trpcClient = createTRPCNext<AppRouter>({
|
|
||||||
config({ ctx }) {
|
|
||||||
return {
|
|
||||||
links: [
|
|
||||||
httpBatchLink({
|
|
||||||
url: `${getBaseUrl()}/api/trpc`,
|
|
||||||
headers() {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Attach headers from app to client requests, so tRPC can add them to context
|
|
||||||
*/
|
|
||||||
[SALEOR_AUTHORIZATION_BEARER_HEADER]: appBridgeInstance?.getState().token,
|
|
||||||
[SALEOR_API_URL_HEADER]: appBridgeInstance?.getState().saleorApiUrl,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
|
|
||||||
};
|
|
||||||
},
|
|
||||||
ssr: true,
|
|
||||||
});
|
|
|
@ -1,13 +0,0 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
|
||||||
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
|
|
||||||
import { inferAsyncReturnType } from "@trpc/server";
|
|
||||||
|
|
||||||
export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
|
|
||||||
return {
|
|
||||||
token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,
|
|
||||||
saleorApiUrl: req.headers[SALEOR_API_URL_HEADER] as string | undefined,
|
|
||||||
appId: undefined as undefined | string,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TrpcContext = inferAsyncReturnType<typeof createTrpcContext>;
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { initTRPC } from "@trpc/server";
|
|
||||||
import { TrpcContext } from "./trpc-context";
|
|
||||||
|
|
||||||
const t = initTRPC.context<TrpcContext>().create();
|
|
||||||
|
|
||||||
export const router = t.router;
|
|
||||||
export const procedure = t.procedure;
|
|
||||||
export const middleware = t.middleware;
|
|
|
@ -1,49 +0,0 @@
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import { AlertBase, Button } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
|
|
||||||
const alertStyle = {
|
|
||||||
marginBottom: 40,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MainInfo = () => {
|
|
||||||
const { appBridge } = useAppBridge();
|
|
||||||
|
|
||||||
const openInNewTab = (url: string) => {
|
|
||||||
appBridge?.dispatch(
|
|
||||||
actions.Redirect({
|
|
||||||
to: url,
|
|
||||||
newContext: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AlertBase style={alertStyle} variant="info">
|
|
||||||
<Typography paragraph>Generate invoices for Orders in your shop</Typography>
|
|
||||||
<Typography paragraph>
|
|
||||||
Open any order and generate invoice. It will be uploaded and attached to the order. App will
|
|
||||||
use Seller data from configuration below
|
|
||||||
</Typography>
|
|
||||||
<div style={{ display: "flex", gap: 20 }}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
openInNewTab("https://github.com/saleor/saleor-app-invoices");
|
|
||||||
}}
|
|
||||||
variant="tertiary"
|
|
||||||
>
|
|
||||||
Repository
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
openInNewTab("https://github.com/saleor/apps/discussions");
|
|
||||||
}}
|
|
||||||
variant="tertiary"
|
|
||||||
>
|
|
||||||
Request a feature
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</AlertBase>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,60 +0,0 @@
|
||||||
import "../styles/globals.css";
|
|
||||||
|
|
||||||
import { Theme } from "@material-ui/core/styles";
|
|
||||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
|
||||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
|
||||||
import { ThemeProvider as MacawUIThemeProvider } from "@saleor/macaw-ui";
|
|
||||||
import React, { PropsWithChildren, useEffect } from "react";
|
|
||||||
import { AppProps } from "next/app";
|
|
||||||
|
|
||||||
import GraphQLProvider from "../providers/GraphQLProvider";
|
|
||||||
import { ThemeSynchronizer } from "@saleor/shared";
|
|
||||||
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
|
||||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
|
||||||
|
|
||||||
const themeOverrides: Partial<Theme> = {
|
|
||||||
/**
|
|
||||||
* You can override MacawUI theme here
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure instance is a singleton.
|
|
||||||
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
|
|
||||||
*/
|
|
||||||
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* That's a hack required by Macaw-UI incompatibility with React@18
|
|
||||||
*/
|
|
||||||
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
|
||||||
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean }>
|
|
||||||
>;
|
|
||||||
|
|
||||||
function NextApp({ Component, pageProps }: AppProps) {
|
|
||||||
/**
|
|
||||||
* Configure JSS (used by MacawUI) for SSR. If Macaw is not used, can be removed.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
const jssStyles = document.querySelector("#jss-server-side");
|
|
||||||
if (jssStyles) {
|
|
||||||
jssStyles?.parentElement?.removeChild(jssStyles);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NoSSRWrapper>
|
|
||||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
|
||||||
<GraphQLProvider>
|
|
||||||
<ThemeProvider overrides={themeOverrides} ssr={false}>
|
|
||||||
<ThemeSynchronizer />
|
|
||||||
<RoutePropagator />
|
|
||||||
<Component {...pageProps} />
|
|
||||||
</ThemeProvider>
|
|
||||||
</GraphQLProvider>
|
|
||||||
</AppBridgeProvider>
|
|
||||||
</NoSSRWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default trpcClient.withTRPC(NextApp);
|
|
|
@ -1,39 +0,0 @@
|
||||||
/**
|
|
||||||
* NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher.
|
|
||||||
*
|
|
||||||
* NOTE: If using this with `next` version 12.2.0 or lower, uncomment the
|
|
||||||
* penultimate line in `CustomErrorComponent`.
|
|
||||||
*
|
|
||||||
* This page is loaded by Nextjs:
|
|
||||||
* - on the server, when data-fetching methods throw or reject
|
|
||||||
* - on the client, when `getInitialProps` throws or rejects
|
|
||||||
* - on the client, when a React lifecycle method throws or rejects, and it's
|
|
||||||
* caught by the built-in Nextjs error boundary
|
|
||||||
*
|
|
||||||
* See:
|
|
||||||
* - https://nextjs.org/docs/basic-features/data-fetching/overview
|
|
||||||
* - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props
|
|
||||||
* - https://reactjs.org/docs/error-boundaries.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as Sentry from '@sentry/nextjs';
|
|
||||||
import NextErrorComponent from 'next/error';
|
|
||||||
|
|
||||||
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
|
|
||||||
// Sentry.captureUnderscoreErrorException(props);
|
|
||||||
|
|
||||||
return <NextErrorComponent statusCode={props.statusCode} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
await Sentry.captureUnderscoreErrorException(contextData);
|
|
||||||
|
|
||||||
// This will contain the status code of the response
|
|
||||||
return NextErrorComponent.getInitialProps(contextData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomErrorComponent;
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
|
|
||||||
import { AppManifest } from "@saleor/app-sdk/types";
|
|
||||||
|
|
||||||
import packageJson from "../../../package.json";
|
|
||||||
import { invoiceRequestedWebhook } from "./webhooks/invoice-requested";
|
|
||||||
|
|
||||||
export default createManifestHandler({
|
|
||||||
async manifestFactory(context) {
|
|
||||||
const manifest: AppManifest = {
|
|
||||||
name: packageJson.name,
|
|
||||||
tokenTargetUrl: `${context.appBaseUrl}/api/register`,
|
|
||||||
appUrl: context.appBaseUrl,
|
|
||||||
permissions: ["MANAGE_ORDERS"],
|
|
||||||
id: "app.saleor.invoices",
|
|
||||||
version: packageJson.version,
|
|
||||||
webhooks: [invoiceRequestedWebhook.getWebhookManifest(context.appBaseUrl)],
|
|
||||||
extensions: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
return manifest;
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next";
|
|
||||||
|
|
||||||
import { saleorApp } from "../../../saleor-app";
|
|
||||||
|
|
||||||
const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Required endpoint, called by Saleor to install app.
|
|
||||||
* It will exchange tokens with app, so saleorApp.apl will contain token
|
|
||||||
*/
|
|
||||||
export default createAppRegisterHandler({
|
|
||||||
apl: saleorApp.apl,
|
|
||||||
/**
|
|
||||||
* Prohibit installation from Saleors other than specified by the regex.
|
|
||||||
* Regex source is ENV so if ENV is not set, all installations will be allowed.
|
|
||||||
*/
|
|
||||||
allowedSaleorUrls: [
|
|
||||||
(url) => {
|
|
||||||
if (allowedUrlsPattern) {
|
|
||||||
const regex = new RegExp(allowedUrlsPattern);
|
|
||||||
|
|
||||||
return regex.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
|
||||||
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
|
||||||
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
|
||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
|
||||||
router: appRouter,
|
|
||||||
createContext: createTrpcContext,
|
|
||||||
});
|
|
|
@ -1,225 +0,0 @@
|
||||||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
|
||||||
import { gql } from "urql";
|
|
||||||
import { saleorApp } from "../../../../saleor-app";
|
|
||||||
import {
|
|
||||||
InvoiceRequestedPayloadFragment,
|
|
||||||
OrderPayloadFragment,
|
|
||||||
} from "../../../../generated/graphql";
|
|
||||||
import { createClient } from "../../../lib/graphql";
|
|
||||||
import { SaleorInvoiceUploader } from "../../../modules/invoice-uploader/saleor-invoice-uploader";
|
|
||||||
import { InvoiceCreateNotifier } from "../../../modules/invoice-create-notifier/invoice-create-notifier";
|
|
||||||
import {
|
|
||||||
InvoiceNumberGenerationStrategy,
|
|
||||||
InvoiceNumberGenerator,
|
|
||||||
} from "../../../modules/invoice-number-generator/invoice-number-generator";
|
|
||||||
import { MicroinvoiceInvoiceGenerator } from "../../../modules/invoice-generator/microinvoice/microinvoice-invoice-generator";
|
|
||||||
import { hashInvoiceFilename } from "../../../modules/invoice-file-name/hash-invoice-filename";
|
|
||||||
import { resolveTempPdfFileLocation } from "../../../modules/invoice-file-name/resolve-temp-pdf-file-location";
|
|
||||||
import { appConfigurationRouter } from "../../../modules/app-configuration/app-configuration.router";
|
|
||||||
import { createLogger } from "../../../lib/logger";
|
|
||||||
import { GetAppConfigurationService } from "../../../modules/app-configuration/get-app-configuration.service";
|
|
||||||
|
|
||||||
const OrderPayload = gql`
|
|
||||||
fragment Address on Address {
|
|
||||||
id
|
|
||||||
country {
|
|
||||||
country
|
|
||||||
code
|
|
||||||
}
|
|
||||||
companyName
|
|
||||||
cityArea
|
|
||||||
countryArea
|
|
||||||
streetAddress1
|
|
||||||
streetAddress2
|
|
||||||
postalCode
|
|
||||||
phone
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
city
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment Money on Money {
|
|
||||||
amount
|
|
||||||
currency
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment TaxedMoney on TaxedMoney {
|
|
||||||
currency
|
|
||||||
gross {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
net {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
tax {
|
|
||||||
...Money
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment OrderPayload on Order {
|
|
||||||
shippingPrice {
|
|
||||||
...TaxedMoney
|
|
||||||
}
|
|
||||||
shippingMethodName
|
|
||||||
number
|
|
||||||
|
|
||||||
id
|
|
||||||
billingAddress {
|
|
||||||
...Address
|
|
||||||
}
|
|
||||||
created
|
|
||||||
fulfillments {
|
|
||||||
created
|
|
||||||
}
|
|
||||||
status
|
|
||||||
number
|
|
||||||
total {
|
|
||||||
...TaxedMoney
|
|
||||||
}
|
|
||||||
channel {
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
lines {
|
|
||||||
productName
|
|
||||||
variantName
|
|
||||||
quantity
|
|
||||||
totalPrice {
|
|
||||||
...TaxedMoney
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shippingPrice {
|
|
||||||
...TaxedMoney
|
|
||||||
}
|
|
||||||
shippingMethodName
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const InvoiceCreatedPayloadFragment = gql`
|
|
||||||
${OrderPayload}
|
|
||||||
|
|
||||||
fragment InvoiceRequestedPayload on InvoiceRequested {
|
|
||||||
invoice {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
order {
|
|
||||||
... on Order {
|
|
||||||
...OrderPayload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InvoiceRequestedSubscription = gql`
|
|
||||||
${InvoiceCreatedPayloadFragment}
|
|
||||||
|
|
||||||
subscription InvoiceRequested {
|
|
||||||
event {
|
|
||||||
...InvoiceRequestedPayload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const invoiceRequestedWebhook = new SaleorAsyncWebhook<InvoiceRequestedPayloadFragment>({
|
|
||||||
name: "Invoice requested",
|
|
||||||
webhookPath: "api/webhooks/invoice-requested",
|
|
||||||
asyncEvent: "INVOICE_REQUESTED",
|
|
||||||
apl: saleorApp.apl,
|
|
||||||
subscriptionQueryAst: InvoiceRequestedSubscription,
|
|
||||||
});
|
|
||||||
|
|
||||||
const invoiceNumberGenerator = new InvoiceNumberGenerator();
|
|
||||||
|
|
||||||
export const handler: NextWebhookApiHandler<InvoiceRequestedPayloadFragment> = async (
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
context
|
|
||||||
) => {
|
|
||||||
const { authData, payload, baseUrl } = context;
|
|
||||||
const logger = createLogger({ domain: authData.saleorApiUrl, url: baseUrl });
|
|
||||||
|
|
||||||
const order = payload.order;
|
|
||||||
|
|
||||||
logger.info({ orderId: order.id }, "Received event INVOICE_REQUESTED");
|
|
||||||
logger.debug(order, "Order from payload:");
|
|
||||||
|
|
||||||
const orderId = order.id;
|
|
||||||
/**
|
|
||||||
* TODO -> should generate from generation date or order date?
|
|
||||||
*/
|
|
||||||
const invoiceName = invoiceNumberGenerator.generateFromOrder(
|
|
||||||
order as OrderPayloadFragment,
|
|
||||||
InvoiceNumberGenerationStrategy.localizedDate("en-US") // todo connect locale -> where from?
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug({ invoiceName }, "Generated invoice name");
|
|
||||||
|
|
||||||
if (!authData) {
|
|
||||||
logger.error("Auth data not found");
|
|
||||||
|
|
||||||
return res.status(403).json({
|
|
||||||
error: `Could not find auth data. Check if app is installed.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const client = createClient(authData.saleorApiUrl, async () =>
|
|
||||||
Promise.resolve({ token: authData.token })
|
|
||||||
);
|
|
||||||
|
|
||||||
const hashedInvoiceName = hashInvoiceFilename(invoiceName, orderId);
|
|
||||||
logger.debug({ hashedInvoiceName });
|
|
||||||
|
|
||||||
const hashedInvoiceFileName = `${hashedInvoiceName}.pdf`;
|
|
||||||
const tempPdfLocation = resolveTempPdfFileLocation(hashedInvoiceFileName);
|
|
||||||
logger.debug({ tempPdfLocation });
|
|
||||||
|
|
||||||
const appConfig = await new GetAppConfigurationService({
|
|
||||||
saleorApiUrl: authData.saleorApiUrl,
|
|
||||||
apiClient: client,
|
|
||||||
}).getConfiguration();
|
|
||||||
|
|
||||||
await new MicroinvoiceInvoiceGenerator()
|
|
||||||
.generate({
|
|
||||||
order,
|
|
||||||
invoiceNumber: invoiceName,
|
|
||||||
filename: tempPdfLocation,
|
|
||||||
companyAddressData: appConfig.shopConfigPerChannel[order.channel.slug]?.address,
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
logger.error(err, "Error generating invoice");
|
|
||||||
|
|
||||||
return res.status(500).json({
|
|
||||||
error: "Error generating invoice",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploader = new SaleorInvoiceUploader(client);
|
|
||||||
|
|
||||||
const uploadedFileUrl = await uploader.upload(tempPdfLocation, `${invoiceName}.pdf`);
|
|
||||||
logger.info({ uploadedFileUrl }, "Uploaded file to storage, will notify Saleor now");
|
|
||||||
|
|
||||||
await new InvoiceCreateNotifier(client).notifyInvoiceCreated(
|
|
||||||
orderId,
|
|
||||||
invoiceName,
|
|
||||||
uploadedFileUrl
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(e);
|
|
||||||
|
|
||||||
return res.status(500).json({
|
|
||||||
error: (e as any)?.message ?? "Error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Success");
|
|
||||||
|
|
||||||
return res.status(200).end();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default invoiceRequestedWebhook.createHandler(handler);
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
api: {
|
|
||||||
bodyParser: false,
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { NextPage } from "next";
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { ChannelsConfiguration } from "../modules/app-configuration/ui/channels-configuration";
|
|
||||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { MainInfo } from "../modules/ui/main-info";
|
|
||||||
|
|
||||||
type Tab = "channels";
|
|
||||||
|
|
||||||
const ConfigurationPage: NextPage = () => {
|
|
||||||
const [activeTab, setActiveTab] = React.useState<Tab>("channels");
|
|
||||||
const channels = trpcClient.channels.fetch.useQuery();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (channels.isSuccess && channels.data.length === 0) {
|
|
||||||
router.push("/not-ready");
|
|
||||||
}
|
|
||||||
}, [channels.data, channels.isSuccess]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Saleor Invoices</h1>
|
|
||||||
<MainInfo />
|
|
||||||
|
|
||||||
{/* Enable if more config available */}
|
|
||||||
{/*<PageTabs*/}
|
|
||||||
{/* style={{ marginBottom: 20 }}*/}
|
|
||||||
{/* value={activeTab}*/}
|
|
||||||
{/* onChange={(e) => setActiveTab(e as Tab)}*/}
|
|
||||||
{/*>*/}
|
|
||||||
{/* <PageTab value="channels" label="Channels configuration" />*/}
|
|
||||||
{/*</PageTabs>*/}
|
|
||||||
{/*<Divider style={{ marginBottom: 20 }} />*/}
|
|
||||||
|
|
||||||
{activeTab === "channels" && <ChannelsConfiguration />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConfigurationPage;
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { NextPage } from "next";
|
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useIsMounted } from "usehooks-ts";
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
|
|
||||||
const IndexPage: NextPage = () => {
|
|
||||||
const { appBridgeState } = useAppBridge();
|
|
||||||
const isMounted = useIsMounted();
|
|
||||||
const { replace } = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMounted() && appBridgeState?.ready) {
|
|
||||||
replace("/configuration");
|
|
||||||
}
|
|
||||||
}, [isMounted, appBridgeState?.ready]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Saleor Invoices</h1>
|
|
||||||
<p>This is Saleor App that allows invoices generation</p>
|
|
||||||
<p>Install app in your Saleor instance and open in with Dashboard</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IndexPage;
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { AlertBase, Button } from "@saleor/macaw-ui";
|
|
||||||
import React from "react";
|
|
||||||
import { Typography } from "@material-ui/core";
|
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
|
|
||||||
const NotReadyPage = () => {
|
|
||||||
const { appBridge } = useAppBridge();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Saleor Invoices App</h1>
|
|
||||||
<AlertBase variant="error">
|
|
||||||
<Typography variant="h3" paragraph>
|
|
||||||
App can not be used
|
|
||||||
</Typography>
|
|
||||||
<Typography paragraph>
|
|
||||||
To configure Invoices App you need to create at least 1 channel
|
|
||||||
</Typography>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => {
|
|
||||||
appBridge?.dispatch(
|
|
||||||
actions.Redirect({
|
|
||||||
to: `/channels/add`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Set up channel
|
|
||||||
</Button>
|
|
||||||
</AlertBase>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NotReadyPage;
|
|
|
@ -1,21 +0,0 @@
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
import { PropsWithChildren } from "react";
|
|
||||||
import { Provider } from "urql";
|
|
||||||
|
|
||||||
import { createClient } from "../lib/graphql";
|
|
||||||
|
|
||||||
function GraphQLProvider(props: PropsWithChildren<{}>) {
|
|
||||||
const { appBridgeState } = useAppBridge();
|
|
||||||
|
|
||||||
if (!appBridgeState?.saleorApiUrl) {
|
|
||||||
return <div {...props}></div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = createClient(appBridgeState?.saleorApiUrl, async () =>
|
|
||||||
Promise.resolve({ token: appBridgeState?.token! })
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Provider value={client} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GraphQLProvider;
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
|
@ -1,4 +0,0 @@
|
||||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1 +0,0 @@
|
||||||
export {};
|
|
|
@ -1,22 +0,0 @@
|
||||||
body {
|
|
||||||
font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
|
||||||
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
||||||
color: #111;
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: #f6f8fa;
|
|
||||||
border: 1px solid #eaeaea;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
|
||||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
code::before {
|
|
||||||
content: "$ ";
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true
|
|
||||||
},
|
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
import { defineConfig } from "vitest/config";
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react()],
|
|
||||||
test: {
|
|
||||||
environment: "jsdom",
|
|
||||||
setupFiles: "./src/setup-tests.ts",
|
|
||||||
css: false,
|
|
||||||
coverage: {
|
|
||||||
provider: "c8",
|
|
||||||
reporter: ["text-summary", "cobertura"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
2
apps/slack/.env.example
Normal file
2
apps/slack/.env.example
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Encryption key used by the EncryptedSettingsManager. Required by the production builds
|
||||||
|
SECRET_KEY=
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"extends": ["custom"]
|
"extends": ["next", "prettier"]
|
||||||
}
|
}
|
||||||
|
|
18
apps/slack/.github/workflows/main.yml
vendored
Normal file
18
apps/slack/.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
name: QA
|
||||||
|
on: [pull_request]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: pnpm/action-setup@v2.2.1
|
||||||
|
with:
|
||||||
|
version: 6.19.1
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
cache: "pnpm"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
- name: Check linters
|
||||||
|
run: pnpm lint
|
4
apps/slack/.husky/pre-commit
Executable file
4
apps/slack/.husky/pre-commit
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx pretty-quick --staged
|
1
apps/slack/CODEOWNERS
Normal file
1
apps/slack/CODEOWNERS
Normal file
|
@ -0,0 +1 @@
|
||||||
|
* @saleor/appstore
|
|
@ -9,20 +9,10 @@ const isSentryPropertiesInEnvironment =
|
||||||
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG;
|
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG;
|
||||||
|
|
||||||
const moduleExports = {
|
const moduleExports = {
|
||||||
transpilePackages: ["@saleor/shared"],
|
|
||||||
eslint: {
|
eslint: {
|
||||||
ignoreDuringBuilds: true,
|
ignoreDuringBuilds: true,
|
||||||
},
|
},
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
redirects() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
source: "/",
|
|
||||||
destination: "/configuration",
|
|
||||||
permanent: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
sentry: {
|
sentry: {
|
||||||
disableServerWebpackPlugin: !isSentryPropertiesInEnvironment,
|
disableServerWebpackPlugin: !isSentryPropertiesInEnvironment,
|
||||||
|
|
|
@ -8,29 +8,31 @@
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "pnpm generate && prettier --loglevel warn --write . && eslint --fix .",
|
"lint": "pnpm generate && prettier --loglevel warn --write . && 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",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"saleor": {
|
"saleor": {
|
||||||
"schemaVersion": "3.4"
|
"schemaVersion": "3.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@saleor/shared": "workspace:*",
|
|
||||||
"@material-ui/core": "^4.12.4",
|
"@material-ui/core": "^4.12.4",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@material-ui/icons": "^4.11.3",
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
"@material-ui/lab": "4.0.0-alpha.61",
|
||||||
"@saleor/app-sdk": "0.26.0",
|
"@saleor/app-sdk": "0.27.1",
|
||||||
"@saleor/macaw-ui": "^0.6.3",
|
"@saleor/macaw-ui": "^0.7.2",
|
||||||
"@sentry/nextjs": "^7.30.0",
|
"@sentry/nextjs": "^7.30.0",
|
||||||
"@urql/exchange-auth": "^1.0.0",
|
"@urql/exchange-auth": "^1.0.0",
|
||||||
"eslint-config-next": "12",
|
"clsx": "^1.2.1",
|
||||||
|
"eslint-config-next": "13.1.4",
|
||||||
"graphql": "^16.5.0",
|
"graphql": "^16.5.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"jose": "^4.11.2",
|
"jose": "^4.11.2",
|
||||||
"next": "13.1.3",
|
"next": "13.1.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"urql": "^3.0.3"
|
"urql": "^3.0.3",
|
||||||
|
"usehooks-ts": "^2.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "2.7.0",
|
"@graphql-codegen/cli": "2.7.0",
|
||||||
|
|
|
@ -12,8 +12,8 @@ specifiers:
|
||||||
'@material-ui/core': ^4.12.4
|
'@material-ui/core': ^4.12.4
|
||||||
'@material-ui/icons': ^4.11.3
|
'@material-ui/icons': ^4.11.3
|
||||||
'@material-ui/lab': 4.0.0-alpha.61
|
'@material-ui/lab': 4.0.0-alpha.61
|
||||||
'@saleor/app-sdk': 0.26.0
|
'@saleor/app-sdk': 0.27.1
|
||||||
'@saleor/macaw-ui': ^0.6.3
|
'@saleor/macaw-ui': ^0.7.2
|
||||||
'@sentry/nextjs': ^7.30.0
|
'@sentry/nextjs': ^7.30.0
|
||||||
'@types/node': ^18.7.16
|
'@types/node': ^18.7.16
|
||||||
'@types/react': ^18.0.19
|
'@types/react': ^18.0.19
|
||||||
|
@ -23,9 +23,10 @@ specifiers:
|
||||||
'@urql/exchange-auth': ^1.0.0
|
'@urql/exchange-auth': ^1.0.0
|
||||||
autoprefixer: ^10.4.7
|
autoprefixer: ^10.4.7
|
||||||
clean-publish: ^4.0.1
|
clean-publish: ^4.0.1
|
||||||
|
clsx: ^1.2.1
|
||||||
eslint: ^8.23.1
|
eslint: ^8.23.1
|
||||||
eslint-config-airbnb: ^19.0.4
|
eslint-config-airbnb: ^19.0.4
|
||||||
eslint-config-next: '12'
|
eslint-config-next: 13.1.4
|
||||||
eslint-config-prettier: ^8.5.0
|
eslint-config-prettier: ^8.5.0
|
||||||
eslint-import-resolver-typescript: ^3.5.1
|
eslint-import-resolver-typescript: ^3.5.1
|
||||||
eslint-plugin-import: ^2.26.0
|
eslint-plugin-import: ^2.26.0
|
||||||
|
@ -37,7 +38,7 @@ specifiers:
|
||||||
graphql-tag: ^2.12.6
|
graphql-tag: ^2.12.6
|
||||||
husky: ^8.0.1
|
husky: ^8.0.1
|
||||||
jose: ^4.11.2
|
jose: ^4.11.2
|
||||||
next: 12.3.0
|
next: 13.1.0
|
||||||
postcss: ^8.4.14
|
postcss: ^8.4.14
|
||||||
prettier: ^2.7.1
|
prettier: ^2.7.1
|
||||||
pretty-quick: ^3.1.3
|
pretty-quick: ^3.1.3
|
||||||
|
@ -46,24 +47,27 @@ specifiers:
|
||||||
react-helmet: ^6.1.0
|
react-helmet: ^6.1.0
|
||||||
typescript: 4.8.3
|
typescript: 4.8.3
|
||||||
urql: ^3.0.3
|
urql: ^3.0.3
|
||||||
|
usehooks-ts: ^2.9.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@material-ui/core': 4.12.4_nylzxt5ale4dsv666zkb25cgtm
|
'@material-ui/core': 4.12.4_nylzxt5ale4dsv666zkb25cgtm
|
||||||
'@material-ui/icons': 4.11.3_syzufrrnbgjhwvvotesawp6yai
|
'@material-ui/icons': 4.11.3_syzufrrnbgjhwvvotesawp6yai
|
||||||
'@material-ui/lab': 4.0.0-alpha.61_syzufrrnbgjhwvvotesawp6yai
|
'@material-ui/lab': 4.0.0-alpha.61_syzufrrnbgjhwvvotesawp6yai
|
||||||
'@saleor/app-sdk': 0.26.0_c3hne4hwj64hb7tofigd3bvkji
|
'@saleor/app-sdk': 0.27.1_s75y6mxmikaw2mhqmuko6w7njm
|
||||||
'@saleor/macaw-ui': 0.6.3_3igskpsgnvdgn574z3phvz2joa
|
'@saleor/macaw-ui': 0.7.2_3igskpsgnvdgn574z3phvz2joa
|
||||||
'@sentry/nextjs': 7.30.0_next@12.3.0+react@18.2.0
|
'@sentry/nextjs': 7.30.0_next@13.1.0+react@18.2.0
|
||||||
'@urql/exchange-auth': 1.0.0_graphql@16.5.0
|
'@urql/exchange-auth': 1.0.0_graphql@16.5.0
|
||||||
eslint-config-next: 12.3.4_irgkl5vooow2ydyo6aokmferha
|
clsx: 1.2.1
|
||||||
|
eslint-config-next: 13.1.4_irgkl5vooow2ydyo6aokmferha
|
||||||
graphql: 16.5.0
|
graphql: 16.5.0
|
||||||
graphql-tag: 2.12.6_graphql@16.5.0
|
graphql-tag: 2.12.6_graphql@16.5.0
|
||||||
jose: 4.11.2
|
jose: 4.11.2
|
||||||
next: 12.3.0_biqbaboplfbrettd7655fr4n2y
|
next: 13.1.0_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-helmet: 6.1.0_react@18.2.0
|
react-helmet: 6.1.0_react@18.2.0
|
||||||
urql: 3.0.3_aez2jvt6lsvokp3l4ousdbdxf4
|
urql: 3.0.3_aez2jvt6lsvokp3l4ousdbdxf4
|
||||||
|
usehooks-ts: 2.9.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@graphql-codegen/cli': 2.7.0_siupcgn3wlvluhjnbpgmgner4m
|
'@graphql-codegen/cli': 2.7.0_siupcgn3wlvluhjnbpgmgner4m
|
||||||
|
@ -743,6 +747,7 @@ packages:
|
||||||
|
|
||||||
/@floating-ui/react-dom-interactions/0.5.0_nylzxt5ale4dsv666zkb25cgtm:
|
/@floating-ui/react-dom-interactions/0.5.0_nylzxt5ale4dsv666zkb25cgtm:
|
||||||
resolution: {integrity: sha512-rfON7mkHjCeogd0BSXPa8GBp1TMxEytJQqGVlCouSUonJ4POqdHsqcxRnCh0yAaGVaL/nB/J1vq28V4RdoLszg==}
|
resolution: {integrity: sha512-rfON7mkHjCeogd0BSXPa8GBp1TMxEytJQqGVlCouSUonJ4POqdHsqcxRnCh0yAaGVaL/nB/J1vq28V4RdoLszg==}
|
||||||
|
deprecated: Package renamed to @floating-ui/react
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/react-dom': 0.7.2_nylzxt5ale4dsv666zkb25cgtm
|
'@floating-ui/react-dom': 0.7.2_nylzxt5ale4dsv666zkb25cgtm
|
||||||
aria-hidden: 1.1.3
|
aria-hidden: 1.1.3
|
||||||
|
@ -1487,18 +1492,18 @@ packages:
|
||||||
graphql: 16.5.0
|
graphql: 16.5.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/env/12.3.0:
|
/@next/env/13.1.0:
|
||||||
resolution: {integrity: sha512-PTJpjAFVbzBQ9xXpzMTroShvD5YDIIy46jQ7d4LrWpY+/5a8H90Tm8hE3Hvkc5RBRspVo7kvEOnqQms0A+2Q6w==}
|
resolution: {integrity: sha512-6iNixFzCndH+Bl4FetQzOMjxCJqg8fs0LAlZviig1K6mIjOWH2m2oPcHcOg1Ta5VCe7Bx5KG1Hs+NrWDUkBt9A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next/12.3.4:
|
/@next/eslint-plugin-next/13.1.4:
|
||||||
resolution: {integrity: sha512-BFwj8ykJY+zc1/jWANsDprDIu2MgwPOIKxNVnrKvPs+f5TPegrVnem8uScND+1veT4B7F6VeqgaNLFW1Hzl9Og==}
|
resolution: {integrity: sha512-a/T30+7Q1scom5t3L+wEBkYzCa+bhT/3DTxzxlNy4Xckw2InzcckQGeIi/larDgh5r2fSSJswhYAZEcKtuJiig==}
|
||||||
dependencies:
|
dependencies:
|
||||||
glob: 7.1.7
|
glob: 7.1.7
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/swc-android-arm-eabi/12.3.0:
|
/@next/swc-android-arm-eabi/13.1.0:
|
||||||
resolution: {integrity: sha512-/PuirPnAKsYBw93w/7Q9hqy+KGOU9mjYprZ/faxMUJh/dc6v3rYLxkZKNG9nFPIW4QKNTCnhP40xF9hLnxO+xg==}
|
resolution: {integrity: sha512-ANBZZRjZBV+Sii11ZVxbxSvfIi6dZwu4w+XnJBDmz+0/wtAigpjYWyMkuWZ/RCD7INdusOlU4EgJ99WzWGIDjA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -1506,8 +1511,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-android-arm64/12.3.0:
|
/@next/swc-android-arm64/13.1.0:
|
||||||
resolution: {integrity: sha512-OaI+FhAM6P9B6Ybwbn0Zl8YwWido0lLwhDBi9WiYCh4RQmIXAyVIoIJPHo4fP05+mXaJ/k1trvDvuURvHOq2qw==}
|
resolution: {integrity: sha512-nPwbkS3aZjCIe61wztgjXjIeylijOP8uGtDGjjJVUF3B/5GLVx3ngZu6tjPTMEgaLM0u//HuGK+aZolWUQWE4g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
@ -1515,8 +1520,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-arm64/12.3.0:
|
/@next/swc-darwin-arm64/13.1.0:
|
||||||
resolution: {integrity: sha512-9s4d3Mhii+WFce8o8Jok7WC3Bawkr9wEUU++SJRptjU1L5tsfYJMrSYCACHLhZujziNDLyExe4Hwwsccps1sfg==}
|
resolution: {integrity: sha512-0hUydiAW18jK2uGPnZRdnRQtdB/3ZoPo84A6zH7MJHxAWw9lzVsv3kMg9kgVBBlrivzqdNN8rdgA+eYNxzXU9w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -1524,8 +1529,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64/12.3.0:
|
/@next/swc-darwin-x64/13.1.0:
|
||||||
resolution: {integrity: sha512-2scC4MqUTwGwok+wpVxP+zWp7WcCAVOtutki2E1n99rBOTnUOX6qXkgxSy083yBN6GqwuC/dzHeN7hIKjavfRA==}
|
resolution: {integrity: sha512-3S3iQqJIysklj0Q9gnanuYMzF8H9p+fUVhvSHxVVLcKH4HsE8EGddNkXsaOyznL1kC6vGKw7h6uz1ojaXEafCA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
@ -1533,8 +1538,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-freebsd-x64/12.3.0:
|
/@next/swc-freebsd-x64/13.1.0:
|
||||||
resolution: {integrity: sha512-xAlruUREij/bFa+qsE1tmsP28t7vz02N4ZDHt2lh3uJUniE0Ne9idyIDLc1Ed0IF2RjfgOp4ZVunuS3OM0sngw==}
|
resolution: {integrity: sha512-wAgzwm/em48GIuWq3OYr0BpncMy7c+UA3hsyX+xKh/vb/sOIpQly7JTa+GNdk17s7kprhMfsgzPG3da36NLpkA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [freebsd]
|
os: [freebsd]
|
||||||
|
@ -1542,8 +1547,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm-gnueabihf/12.3.0:
|
/@next/swc-linux-arm-gnueabihf/13.1.0:
|
||||||
resolution: {integrity: sha512-jin2S4VT/cugc2dSZEUIabhYDJNgrUh7fufbdsaAezgcQzqfdfJqfxl4E9GuafzB4cbRPTaqA0V5uqbp0IyGkQ==}
|
resolution: {integrity: sha512-Cr2hzL7ad+4nj9KrR1Cz1RDcsWa61X6I7gc6PToRYIY4gL480Sijq19xo7dlXQPnr1viVzbNiNnNXZASHv7uvw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -1551,8 +1556,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu/12.3.0:
|
/@next/swc-linux-arm64-gnu/13.1.0:
|
||||||
resolution: {integrity: sha512-RqJHDKe0WImeUrdR0kayTkRWgp4vD/MS7g0r6Xuf8+ellOFH7JAAJffDW3ayuVZeMYOa7RvgNFcOoWnrTUl9Nw==}
|
resolution: {integrity: sha512-EjCIKfeZB9h72evL2yGNwBvE5Im96Zn7o2zxImlvCiUYb/xXDqn4hzhck035BSP3g3sGDLfijFTE1wKRyXIk4w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -1560,8 +1565,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl/12.3.0:
|
/@next/swc-linux-arm64-musl/13.1.0:
|
||||||
resolution: {integrity: sha512-nvNWoUieMjvDjpYJ/4SQe9lQs2xMj6ZRs8N+bmTrVu9leY2Fg3WD6W9p/1uU9hGO8u+OdF13wc4iRShu/WYIHg==}
|
resolution: {integrity: sha512-WAsZtCtPXlz/7/bnW9ryw856xEun+c6xSwZwbcvrMxtcSiW3z0LD91Nsj3AkexsjRtBjeEpNeVtDExqF2VKKSA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -1569,8 +1574,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu/12.3.0:
|
/@next/swc-linux-x64-gnu/13.1.0:
|
||||||
resolution: {integrity: sha512-4ajhIuVU9PeQCMMhdDgZTLrHmjbOUFuIyg6J19hZqwEwDTSqQyrSLkbJs2Nd7IRiM6Ul/XyrtEFCpk4k+xD2+w==}
|
resolution: {integrity: sha512-Tjd5gieI3X9vPce5yF+GsQxOl0jwUkyOrTR1g5PQr+bT/9Qos/yPL48H1L5ayEp0hxgLVPW7skGal7lVnAoVEQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -1578,8 +1583,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl/12.3.0:
|
/@next/swc-linux-x64-musl/13.1.0:
|
||||||
resolution: {integrity: sha512-U092RBYbaGxoMAwpauePJEu2PuZSEoUCGJBvsptQr2/2XIMwAJDYM4c/M5NfYEsBr+yjvsYNsOpYfeQ88D82Yg==}
|
resolution: {integrity: sha512-H9UMEQv40e9pkgdX4mCms0dDf2dimmZ6WXhDTWF/yIh9icgcsHaP73BJ9IFlgvh80wLiUgWZ3LAX4vXnXzidmg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
@ -1587,8 +1592,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc/12.3.0:
|
/@next/swc-win32-arm64-msvc/13.1.0:
|
||||||
resolution: {integrity: sha512-pzSzaxjDEJe67bUok9Nxf9rykbJfHXW0owICFsPBsqHyc+cr8vpF7g9e2APTCddtVhvjkga9ILoZJ9NxWS7Yiw==}
|
resolution: {integrity: sha512-LFFIKjW/cPl4wvG8HF/6oYPJZ+Jy32G3FUflC8UW1Od6W9yOSEvadhk9fMyDZN4cgsNOcVc3uVSMpcuuCpbDGw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -1596,8 +1601,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc/12.3.0:
|
/@next/swc-win32-ia32-msvc/13.1.0:
|
||||||
resolution: {integrity: sha512-MQGUpMbYhQmTZ06a9e0hPQJnxFMwETo2WtyAotY3GEzbNCQVbCGhsvqEKcl+ZEHgShlHXUWvSffq1ZscY6gK7A==}
|
resolution: {integrity: sha512-MBLaoHZSenMdxhB3Ww1VNEhjyPT3uLjzAi5Ygk48LLLbOGu5KxQolhINRrqGuJWqJRNWSJ9JSFBfJrZwQzrUew==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -1605,8 +1610,8 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc/12.3.0:
|
/@next/swc-win32-x64-msvc/13.1.0:
|
||||||
resolution: {integrity: sha512-C/nw6OgQpEULWqs+wgMHXGvlJLguPRFFGqR2TAqWBerQ8J+Sg3z1ZTqwelkSi4FoqStGuZ2UdFHIDN1ySmR1xA==}
|
resolution: {integrity: sha512-fFTfIQvnmpbKoyh4v3ezlGqtERlgc2Sx8qJwPuYqoVi0V08wCx9wp2Iq1CINxP3UMHkEeNX7gYpDOd+9Cw9EiQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
@ -1642,7 +1647,6 @@ packages:
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
tiny-glob: 0.2.9
|
tiny-glob: 0.2.9
|
||||||
tslib: 2.4.0
|
tslib: 2.4.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@rollup/plugin-sucrase/4.0.4_rollup@2.78.0:
|
/@rollup/plugin-sucrase/4.0.4_rollup@2.78.0:
|
||||||
resolution: {integrity: sha512-YH4J8yoJb5EVnLhAwWxYAQNh2SJOR+SdZ6XdgoKEv6Kxm33riYkM8MlMaggN87UoISP52qAFyZ5ey56wu6umGg==}
|
resolution: {integrity: sha512-YH4J8yoJb5EVnLhAwWxYAQNh2SJOR+SdZ6XdgoKEv6Kxm33riYkM8MlMaggN87UoISP52qAFyZ5ey56wu6umGg==}
|
||||||
|
@ -1679,8 +1683,8 @@ packages:
|
||||||
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
|
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@saleor/app-sdk/0.26.0_c3hne4hwj64hb7tofigd3bvkji:
|
/@saleor/app-sdk/0.27.1_s75y6mxmikaw2mhqmuko6w7njm:
|
||||||
resolution: {integrity: sha512-OdaWKpvgMBYY/O0RJdHqgyurZXsVJDe1De1n6rU+6Au8C7VusMLQWbjS0VVbV3UJsEKJmbKSgBUSXSD283a01g==}
|
resolution: {integrity: sha512-ZNbucokKCdBE1qa+YLHvjBVazYcRuUExBdaPW9aNxfeYyXgQNCdHqJx9oA/S1lMEVSbZSIRcn8Sx1+X/eEV8BA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: '>=12'
|
next: '>=12'
|
||||||
react: '>=17'
|
react: '>=17'
|
||||||
|
@ -1690,7 +1694,7 @@ packages:
|
||||||
fast-glob: 3.2.11
|
fast-glob: 3.2.11
|
||||||
graphql: 16.6.0
|
graphql: 16.6.0
|
||||||
jose: 4.11.2
|
jose: 4.11.2
|
||||||
next: 12.3.0_biqbaboplfbrettd7655fr4n2y
|
next: 13.1.0_biqbaboplfbrettd7655fr4n2y
|
||||||
raw-body: 2.5.1
|
raw-body: 2.5.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
@ -1700,9 +1704,9 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@saleor/macaw-ui/0.6.3_3igskpsgnvdgn574z3phvz2joa:
|
/@saleor/macaw-ui/0.7.2_3igskpsgnvdgn574z3phvz2joa:
|
||||||
resolution: {integrity: sha512-YJPSho+mQHaZsHZancLbWoHKoF0pNLuB1OeeUz/xFMVkMw3aq1WhRWBvEN3byeshGPw7v7jYTmCtNL22UciiJA==}
|
resolution: {integrity: sha512-Fli7fhTWuHu7q2CzxwTUpB4x9HYaxHSAzCLZLA23VY1ieIWbCxbsXadMiMGWp/nuYitswMr6JXMm+1SDe9K8LQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=16 <19'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@material-ui/core': ^4.11.2
|
'@material-ui/core': ^4.11.2
|
||||||
'@material-ui/icons': ^4.11.2
|
'@material-ui/icons': ^4.11.2
|
||||||
|
@ -1722,7 +1726,7 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
react-helmet: 6.1.0_react@18.2.0
|
react-helmet: 6.1.0_react@18.2.0
|
||||||
react-inlinesvg: 2.3.0_react@18.2.0
|
react-inlinesvg: 3.0.1_react@18.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -1793,7 +1797,7 @@ packages:
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@sentry/nextjs/7.30.0_next@12.3.0+react@18.2.0:
|
/@sentry/nextjs/7.30.0_next@13.1.0+react@18.2.0:
|
||||||
resolution: {integrity: sha512-4gW29QRb9S7AHf8/uJ77xc18JwmFFqdGyZ8YHRSoRhuof9IM0i8sK/NYUaPolaMRHYc3gf8gJsHjH/0AaUXhQA==}
|
resolution: {integrity: sha512-4gW29QRb9S7AHf8/uJ77xc18JwmFFqdGyZ8YHRSoRhuof9IM0i8sK/NYUaPolaMRHYc3gf8gJsHjH/0AaUXhQA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1815,7 +1819,7 @@ packages:
|
||||||
'@sentry/utils': 7.30.0
|
'@sentry/utils': 7.30.0
|
||||||
'@sentry/webpack-plugin': 1.20.0
|
'@sentry/webpack-plugin': 1.20.0
|
||||||
chalk: 3.0.0
|
chalk: 3.0.0
|
||||||
next: 12.3.0_biqbaboplfbrettd7655fr4n2y
|
next: 13.1.0_biqbaboplfbrettd7655fr4n2y
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
rollup: 2.78.0
|
rollup: 2.78.0
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
|
@ -1904,8 +1908,8 @@ packages:
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@swc/helpers/0.4.11:
|
/@swc/helpers/0.4.14:
|
||||||
resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==}
|
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.4.0
|
tslib: 2.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -2043,6 +2047,27 @@ packages:
|
||||||
typescript: 4.8.3
|
typescript: 4.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@typescript-eslint/parser/5.48.2_irgkl5vooow2ydyo6aokmferha:
|
||||||
|
resolution: {integrity: sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
typescript: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/scope-manager': 5.48.2
|
||||||
|
'@typescript-eslint/types': 5.48.2
|
||||||
|
'@typescript-eslint/typescript-estree': 5.48.2_typescript@4.8.3
|
||||||
|
debug: 4.3.4
|
||||||
|
eslint: 8.23.1
|
||||||
|
typescript: 4.8.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/scope-manager/5.36.2:
|
/@typescript-eslint/scope-manager/5.36.2:
|
||||||
resolution: {integrity: sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==}
|
resolution: {integrity: sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==}
|
||||||
|
@ -2050,6 +2075,15 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.36.2
|
'@typescript-eslint/types': 5.36.2
|
||||||
'@typescript-eslint/visitor-keys': 5.36.2
|
'@typescript-eslint/visitor-keys': 5.36.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@typescript-eslint/scope-manager/5.48.2:
|
||||||
|
resolution: {integrity: sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 5.48.2
|
||||||
|
'@typescript-eslint/visitor-keys': 5.48.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/type-utils/5.36.2_irgkl5vooow2ydyo6aokmferha:
|
/@typescript-eslint/type-utils/5.36.2_irgkl5vooow2ydyo6aokmferha:
|
||||||
resolution: {integrity: sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==}
|
resolution: {integrity: sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==}
|
||||||
|
@ -2074,6 +2108,12 @@ packages:
|
||||||
/@typescript-eslint/types/5.36.2:
|
/@typescript-eslint/types/5.36.2:
|
||||||
resolution: {integrity: sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==}
|
resolution: {integrity: sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@typescript-eslint/types/5.48.2:
|
||||||
|
resolution: {integrity: sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/typescript-estree/5.36.2_typescript@4.8.3:
|
/@typescript-eslint/typescript-estree/5.36.2_typescript@4.8.3:
|
||||||
resolution: {integrity: sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==}
|
resolution: {integrity: sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==}
|
||||||
|
@ -2094,6 +2134,28 @@ packages:
|
||||||
typescript: 4.8.3
|
typescript: 4.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@typescript-eslint/typescript-estree/5.48.2_typescript@4.8.3:
|
||||||
|
resolution: {integrity: sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 5.48.2
|
||||||
|
'@typescript-eslint/visitor-keys': 5.48.2
|
||||||
|
debug: 4.3.4
|
||||||
|
globby: 11.1.0
|
||||||
|
is-glob: 4.0.3
|
||||||
|
semver: 7.3.7
|
||||||
|
tsutils: 3.21.0_typescript@4.8.3
|
||||||
|
typescript: 4.8.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@typescript-eslint/utils/5.36.2_irgkl5vooow2ydyo6aokmferha:
|
/@typescript-eslint/utils/5.36.2_irgkl5vooow2ydyo6aokmferha:
|
||||||
resolution: {integrity: sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==}
|
resolution: {integrity: sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==}
|
||||||
|
@ -2119,6 +2181,15 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.36.2
|
'@typescript-eslint/types': 5.36.2
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@typescript-eslint/visitor-keys/5.48.2:
|
||||||
|
resolution: {integrity: sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 5.48.2
|
||||||
|
eslint-visitor-keys: 3.3.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@urql/core/3.0.3_graphql@16.5.0:
|
/@urql/core/3.0.3_graphql@16.5.0:
|
||||||
resolution: {integrity: sha512-raQP51ERNtg5BvlN8x8mHVRvk4K0ugWQ69n53BdkjKpXVV5kuWp7trnwriGv1fQKa8HuiGNSCfyslUucc0OVQg==}
|
resolution: {integrity: sha512-raQP51ERNtg5BvlN8x8mHVRvk4K0ugWQ69n53BdkjKpXVV5kuWp7trnwriGv1fQKa8HuiGNSCfyslUucc0OVQg==}
|
||||||
|
@ -2525,6 +2596,11 @@ packages:
|
||||||
|
|
||||||
/caniuse-lite/1.0.30001374:
|
/caniuse-lite/1.0.30001374:
|
||||||
resolution: {integrity: sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==}
|
resolution: {integrity: sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/caniuse-lite/1.0.30001447:
|
||||||
|
resolution: {integrity: sha512-bdKU1BQDPeEXe9A39xJnGtY0uRq/z5osrnXUw0TcK+EYno45Y+U7QU9HhHEyzvMDffpYadFXi3idnSNkcwLkTw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/capital-case/1.0.4:
|
/capital-case/1.0.4:
|
||||||
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
|
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
|
||||||
|
@ -2662,6 +2738,10 @@ packages:
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/client-only/0.0.1:
|
||||||
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cliui/6.0.0:
|
/cliui/6.0.0:
|
||||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2920,7 +3000,6 @@ packages:
|
||||||
/define-lazy-prop/2.0.0:
|
/define-lazy-prop/2.0.0:
|
||||||
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/define-properties/1.1.4:
|
/define-properties/1.1.4:
|
||||||
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
|
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
|
||||||
|
@ -3050,7 +3129,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.10
|
graceful-fs: 4.2.10
|
||||||
tapable: 2.2.1
|
tapable: 2.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/error-ex/1.3.2:
|
/error-ex/1.3.2:
|
||||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||||
|
@ -3148,8 +3226,8 @@ packages:
|
||||||
object.entries: 1.1.5
|
object.entries: 1.1.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-config-next/12.3.4_irgkl5vooow2ydyo6aokmferha:
|
/eslint-config-next/13.1.4_irgkl5vooow2ydyo6aokmferha:
|
||||||
resolution: {integrity: sha512-WuT3gvgi7Bwz00AOmKGhOeqnyA5P29Cdyr0iVjLyfDbk+FANQKcOjFUTZIdyYfe5Tq1x4TGcmoe4CwctGvFjHQ==}
|
resolution: {integrity: sha512-r7n9V4/kkiDDVFfBwI3tviGUV/jUzGI0lY3JefxceYaU18gdk2kMgNPyhHobowu1+yHZpZi8iEzRtzeTrtGRLg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.23.0 || ^8.0.0
|
eslint: ^7.23.0 || ^8.0.0
|
||||||
typescript: '>=3.3.1'
|
typescript: '>=3.3.1'
|
||||||
|
@ -3157,13 +3235,13 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 12.3.4
|
'@next/eslint-plugin-next': 13.1.4
|
||||||
'@rushstack/eslint-patch': 1.2.0
|
'@rushstack/eslint-patch': 1.2.0
|
||||||
'@typescript-eslint/parser': 5.36.2_irgkl5vooow2ydyo6aokmferha
|
'@typescript-eslint/parser': 5.48.2_irgkl5vooow2ydyo6aokmferha
|
||||||
eslint: 8.23.1
|
eslint: 8.23.1
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint-import-resolver-node: 0.3.6
|
||||||
eslint-import-resolver-typescript: 2.7.1_hdzsmr7kawaomymueo2tso6fjq
|
eslint-import-resolver-typescript: 3.5.3_hdzsmr7kawaomymueo2tso6fjq
|
||||||
eslint-plugin-import: 2.26.0_ablb3uaum6qywgmkr7jjhbmzda
|
eslint-plugin-import: 2.26.0_yvykkuql7q5wwoyd3vgrje6aqa
|
||||||
eslint-plugin-jsx-a11y: 6.6.0_eslint@8.23.1
|
eslint-plugin-jsx-a11y: 6.6.0_eslint@8.23.1
|
||||||
eslint-plugin-react: 7.31.8_eslint@8.23.1
|
eslint-plugin-react: 7.31.8_eslint@8.23.1
|
||||||
eslint-plugin-react-hooks: 4.6.0_eslint@8.23.1
|
eslint-plugin-react-hooks: 4.6.0_eslint@8.23.1
|
||||||
|
@ -3190,24 +3268,6 @@ packages:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
/eslint-import-resolver-typescript/2.7.1_hdzsmr7kawaomymueo2tso6fjq:
|
|
||||||
resolution: {integrity: sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
peerDependencies:
|
|
||||||
eslint: '*'
|
|
||||||
eslint-plugin-import: '*'
|
|
||||||
dependencies:
|
|
||||||
debug: 4.3.4
|
|
||||||
eslint: 8.23.1
|
|
||||||
eslint-plugin-import: 2.26.0_ablb3uaum6qywgmkr7jjhbmzda
|
|
||||||
glob: 7.2.3
|
|
||||||
is-glob: 4.0.3
|
|
||||||
resolve: 1.22.1
|
|
||||||
tsconfig-paths: 3.14.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/eslint-import-resolver-typescript/3.5.1_hdzsmr7kawaomymueo2tso6fjq:
|
/eslint-import-resolver-typescript/3.5.1_hdzsmr7kawaomymueo2tso6fjq:
|
||||||
resolution: {integrity: sha512-U7LUjNJPYjNsHvAUAkt/RU3fcTSpbllA0//35B4eLYTX74frmOepbt7F7J3D1IGtj9k21buOpaqtDd4ZlS/BYQ==}
|
resolution: {integrity: sha512-U7LUjNJPYjNsHvAUAkt/RU3fcTSpbllA0//35B4eLYTX74frmOepbt7F7J3D1IGtj9k21buOpaqtDd4ZlS/BYQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
|
@ -3228,29 +3288,22 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-module-utils/2.7.3_2zrz7rhjbmz575pf362efqksom:
|
/eslint-import-resolver-typescript/3.5.3_hdzsmr7kawaomymueo2tso6fjq:
|
||||||
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
|
resolution: {integrity: sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': '*'
|
eslint: '*'
|
||||||
eslint-import-resolver-node: '*'
|
eslint-plugin-import: '*'
|
||||||
eslint-import-resolver-typescript: '*'
|
|
||||||
eslint-import-resolver-webpack: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@typescript-eslint/parser':
|
|
||||||
optional: true
|
|
||||||
eslint-import-resolver-node:
|
|
||||||
optional: true
|
|
||||||
eslint-import-resolver-typescript:
|
|
||||||
optional: true
|
|
||||||
eslint-import-resolver-webpack:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/parser': 5.36.2_irgkl5vooow2ydyo6aokmferha
|
debug: 4.3.4
|
||||||
debug: 3.2.7
|
enhanced-resolve: 5.10.0
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint: 8.23.1
|
||||||
eslint-import-resolver-typescript: 2.7.1_hdzsmr7kawaomymueo2tso6fjq
|
eslint-plugin-import: 2.26.0_yvykkuql7q5wwoyd3vgrje6aqa
|
||||||
find-up: 2.1.0
|
get-tsconfig: 4.2.0
|
||||||
|
globby: 13.1.2
|
||||||
|
is-core-module: 2.10.0
|
||||||
|
is-glob: 4.0.3
|
||||||
|
synckit: 0.8.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -3282,34 +3335,30 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-import/2.26.0_ablb3uaum6qywgmkr7jjhbmzda:
|
/eslint-module-utils/2.7.3_ldxyliihv5g6tibz4or2rt2p7i:
|
||||||
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': '*'
|
'@typescript-eslint/parser': '*'
|
||||||
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
eslint-import-resolver-node: '*'
|
||||||
|
eslint-import-resolver-typescript: '*'
|
||||||
|
eslint-import-resolver-webpack: '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
optional: true
|
optional: true
|
||||||
|
eslint-import-resolver-node:
|
||||||
|
optional: true
|
||||||
|
eslint-import-resolver-typescript:
|
||||||
|
optional: true
|
||||||
|
eslint-import-resolver-webpack:
|
||||||
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/parser': 5.36.2_irgkl5vooow2ydyo6aokmferha
|
'@typescript-eslint/parser': 5.48.2_irgkl5vooow2ydyo6aokmferha
|
||||||
array-includes: 3.1.5
|
debug: 3.2.7
|
||||||
array.prototype.flat: 1.3.0
|
|
||||||
debug: 2.6.9
|
|
||||||
doctrine: 2.1.0
|
|
||||||
eslint: 8.23.1
|
|
||||||
eslint-import-resolver-node: 0.3.6
|
eslint-import-resolver-node: 0.3.6
|
||||||
eslint-module-utils: 2.7.3_2zrz7rhjbmz575pf362efqksom
|
eslint-import-resolver-typescript: 3.5.3_hdzsmr7kawaomymueo2tso6fjq
|
||||||
has: 1.0.3
|
find-up: 2.1.0
|
||||||
is-core-module: 2.9.0
|
|
||||||
is-glob: 4.0.3
|
|
||||||
minimatch: 3.1.2
|
|
||||||
object.values: 1.1.5
|
|
||||||
resolve: 1.22.1
|
|
||||||
tsconfig-paths: 3.14.1
|
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- eslint-import-resolver-typescript
|
|
||||||
- eslint-import-resolver-webpack
|
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
@ -3344,6 +3393,37 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/eslint-plugin-import/2.26.0_yvykkuql7q5wwoyd3vgrje6aqa:
|
||||||
|
resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
peerDependencies:
|
||||||
|
'@typescript-eslint/parser': '*'
|
||||||
|
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/parser': 5.48.2_irgkl5vooow2ydyo6aokmferha
|
||||||
|
array-includes: 3.1.5
|
||||||
|
array.prototype.flat: 1.3.0
|
||||||
|
debug: 2.6.9
|
||||||
|
doctrine: 2.1.0
|
||||||
|
eslint: 8.23.1
|
||||||
|
eslint-import-resolver-node: 0.3.6
|
||||||
|
eslint-module-utils: 2.7.3_ldxyliihv5g6tibz4or2rt2p7i
|
||||||
|
has: 1.0.3
|
||||||
|
is-core-module: 2.9.0
|
||||||
|
is-glob: 4.0.3
|
||||||
|
minimatch: 3.1.2
|
||||||
|
object.values: 1.1.5
|
||||||
|
resolve: 1.22.1
|
||||||
|
tsconfig-paths: 3.14.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- eslint-import-resolver-typescript
|
||||||
|
- eslint-import-resolver-webpack
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/eslint-plugin-jsx-a11y/6.6.0_eslint@8.23.1:
|
/eslint-plugin-jsx-a11y/6.6.0_eslint@8.23.1:
|
||||||
resolution: {integrity: sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==}
|
resolution: {integrity: sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
|
@ -3782,7 +3862,6 @@ packages:
|
||||||
|
|
||||||
/get-tsconfig/4.2.0:
|
/get-tsconfig/4.2.0:
|
||||||
resolution: {integrity: sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==}
|
resolution: {integrity: sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/glob-parent/5.1.2:
|
/glob-parent/5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
|
@ -3841,7 +3920,6 @@ packages:
|
||||||
|
|
||||||
/globalyzer/0.1.0:
|
/globalyzer/0.1.0:
|
||||||
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
|
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/globby/11.1.0:
|
/globby/11.1.0:
|
||||||
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
|
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
|
||||||
|
@ -3863,11 +3941,9 @@ packages:
|
||||||
ignore: 5.2.0
|
ignore: 5.2.0
|
||||||
merge2: 1.4.1
|
merge2: 1.4.1
|
||||||
slash: 4.0.0
|
slash: 4.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/globrex/0.1.2:
|
/globrex/0.1.2:
|
||||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/got/9.6.0:
|
/got/9.6.0:
|
||||||
resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==}
|
resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==}
|
||||||
|
@ -3890,7 +3966,6 @@ packages:
|
||||||
|
|
||||||
/graceful-fs/4.2.10:
|
/graceful-fs/4.2.10:
|
||||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/grapheme-splitter/1.0.4:
|
/grapheme-splitter/1.0.4:
|
||||||
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
||||||
|
@ -4215,7 +4290,6 @@ packages:
|
||||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/is-extglob/2.1.1:
|
/is-extglob/2.1.1:
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
|
@ -4356,7 +4430,6 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/isarray/1.0.0:
|
/isarray/1.0.0:
|
||||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
|
@ -4906,15 +4979,15 @@ packages:
|
||||||
/natural-compare/1.4.0:
|
/natural-compare/1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
/next/12.3.0_biqbaboplfbrettd7655fr4n2y:
|
/next/13.1.0_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-GpzI6me9V1+XYtfK0Ae9WD0mKqHyzQlGq1xH1rzNIYMASo4Tkl4rTe9jSqtBpXFhOS33KohXs9ZY38Akkhdciw==}
|
resolution: {integrity: sha512-lQMZH1V94L5IL/WaihQkTYabSY73aqgrkGPJB5uz+2O3ES4I3losV/maXLY7l7x5e+oNyE9N81upNQ8uRsR5/A==}
|
||||||
engines: {node: '>=12.22.0'}
|
engines: {node: '>=14.6.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
fibers: '>= 3.1.0'
|
fibers: '>= 3.1.0'
|
||||||
node-sass: ^6.0.0 || ^7.0.0
|
node-sass: ^6.0.0 || ^7.0.0
|
||||||
react: ^17.0.2 || ^18.0.0-0
|
react: ^18.2.0
|
||||||
react-dom: ^17.0.2 || ^18.0.0-0
|
react-dom: ^18.2.0
|
||||||
sass: ^1.3.0
|
sass: ^1.3.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
fibers:
|
fibers:
|
||||||
|
@ -4924,28 +4997,27 @@ packages:
|
||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 12.3.0
|
'@next/env': 13.1.0
|
||||||
'@swc/helpers': 0.4.11
|
'@swc/helpers': 0.4.14
|
||||||
caniuse-lite: 1.0.30001374
|
caniuse-lite: 1.0.30001447
|
||||||
postcss: 8.4.14
|
postcss: 8.4.14
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
styled-jsx: 5.0.6_react@18.2.0
|
styled-jsx: 5.1.1_react@18.2.0
|
||||||
use-sync-external-store: 1.2.0_react@18.2.0
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-android-arm-eabi': 12.3.0
|
'@next/swc-android-arm-eabi': 13.1.0
|
||||||
'@next/swc-android-arm64': 12.3.0
|
'@next/swc-android-arm64': 13.1.0
|
||||||
'@next/swc-darwin-arm64': 12.3.0
|
'@next/swc-darwin-arm64': 13.1.0
|
||||||
'@next/swc-darwin-x64': 12.3.0
|
'@next/swc-darwin-x64': 13.1.0
|
||||||
'@next/swc-freebsd-x64': 12.3.0
|
'@next/swc-freebsd-x64': 13.1.0
|
||||||
'@next/swc-linux-arm-gnueabihf': 12.3.0
|
'@next/swc-linux-arm-gnueabihf': 13.1.0
|
||||||
'@next/swc-linux-arm64-gnu': 12.3.0
|
'@next/swc-linux-arm64-gnu': 13.1.0
|
||||||
'@next/swc-linux-arm64-musl': 12.3.0
|
'@next/swc-linux-arm64-musl': 13.1.0
|
||||||
'@next/swc-linux-x64-gnu': 12.3.0
|
'@next/swc-linux-x64-gnu': 13.1.0
|
||||||
'@next/swc-linux-x64-musl': 12.3.0
|
'@next/swc-linux-x64-musl': 13.1.0
|
||||||
'@next/swc-win32-arm64-msvc': 12.3.0
|
'@next/swc-win32-arm64-msvc': 13.1.0
|
||||||
'@next/swc-win32-ia32-msvc': 12.3.0
|
'@next/swc-win32-ia32-msvc': 13.1.0
|
||||||
'@next/swc-win32-x64-msvc': 12.3.0
|
'@next/swc-win32-x64-msvc': 13.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
@ -5104,7 +5176,6 @@ packages:
|
||||||
define-lazy-prop: 2.0.0
|
define-lazy-prop: 2.0.0
|
||||||
is-docker: 2.2.1
|
is-docker: 2.2.1
|
||||||
is-wsl: 2.2.0
|
is-wsl: 2.2.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/optionator/0.9.1:
|
/optionator/0.9.1:
|
||||||
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
|
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
|
||||||
|
@ -5438,10 +5509,10 @@ packages:
|
||||||
react-side-effect: 2.1.2_react@18.2.0
|
react-side-effect: 2.1.2_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-inlinesvg/2.3.0_react@18.2.0:
|
/react-inlinesvg/3.0.1_react@18.2.0:
|
||||||
resolution: {integrity: sha512-fEGOdDf4k4bcveArbEpj01pJcH8pOCKLxmSj2POFdGvEk5YK0NZVnH6BXpW/PzACHPRsuh1YKAhNZyFnD28oxg==}
|
resolution: {integrity: sha512-cBfoyfseNI2PkDA7ZKIlDoHq0eMfpoC3DhKBQNC+/X1M4ZQB+aXW+YiNPUDDDKXUsGDUIZWWiZWNFeauDIVdoA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
exenv: 1.2.2
|
exenv: 1.2.2
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
@ -5758,7 +5829,6 @@ packages:
|
||||||
/slash/4.0.0:
|
/slash/4.0.0:
|
||||||
resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
|
resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/slice-ansi/0.0.4:
|
/slice-ansi/0.0.4:
|
||||||
resolution: {integrity: sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=}
|
resolution: {integrity: sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=}
|
||||||
|
@ -5906,8 +5976,8 @@ packages:
|
||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
/styled-jsx/5.0.6_react@18.2.0:
|
/styled-jsx/5.1.1_react@18.2.0:
|
||||||
resolution: {integrity: sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA==}
|
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': '*'
|
'@babel/core': '*'
|
||||||
|
@ -5919,6 +5989,7 @@ packages:
|
||||||
babel-plugin-macros:
|
babel-plugin-macros:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
client-only: 0.0.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
@ -5984,12 +6055,10 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@pkgr/utils': 2.3.1
|
'@pkgr/utils': 2.3.1
|
||||||
tslib: 2.4.0
|
tslib: 2.4.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/tapable/2.2.1:
|
/tapable/2.2.1:
|
||||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/text-table/0.2.0:
|
/text-table/0.2.0:
|
||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
|
@ -6016,7 +6085,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
globalyzer: 0.1.0
|
globalyzer: 0.1.0
|
||||||
globrex: 0.1.2
|
globrex: 0.1.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/tiny-warning/1.0.3:
|
/tiny-warning/1.0.3:
|
||||||
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
|
||||||
|
@ -6224,12 +6292,15 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-sync-external-store/1.2.0_react@18.2.0:
|
/usehooks-ts/2.9.1_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
resolution: {integrity: sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==}
|
||||||
|
engines: {node: '>=16.15.0', npm: '>=8'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/util-deprecate/1.0.2:
|
/util-deprecate/1.0.2:
|
||||||
|
|
31
apps/slack/src/assets/saleor-logo-dark.svg
Normal file
31
apps/slack/src/assets/saleor-logo-dark.svg
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<svg width="654" height="261" viewBox="0 0 654 261" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_229_18)">
|
||||||
|
<g filter="url(#filter1_d_229_18)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M283.69 230.761H315.827V62.9351L283.69 95.0157V230.761ZM199.874 194.213C199.874 205.089 205.229 208.352 220.182 208.352C232.011 208.352 237.366 202.696 237.366 191.82V180.509H221.298C205.229 180.509 199.874 184.424 199.874 194.213ZM237.145 218.359C231.566 226.625 223.754 232.281 206.347 232.281C181.129 232.281 168.853 220.535 168.853 196.173C168.853 171.375 182.468 159.195 222.415 159.195H237.145V151.146C237.145 142.444 235.137 137.223 222.639 137.223C209.695 137.223 206.793 140.27 204.785 150.275L173.541 145.49C178.228 121.997 190.055 112.427 223.754 112.427C258.568 112.427 268.611 125.696 268.611 152.015V230.759H237.145V218.359ZM161.534 194.652C161.534 216.405 151.713 232.284 112.658 232.284C78.7365 232.284 65.1231 222.931 62.2222 199.656L93.0196 195.088C94.8048 206.616 99.0449 209.009 113.104 209.009C125.827 209.009 130.067 205.964 130.067 199.003C130.067 190.519 125.155 188.345 112.435 185.951C111.422 185.779 110.391 185.609 109.348 185.436C89.2411 182.106 64.242 177.966 64.4535 149.842C64.4535 127.004 76.9513 112.648 112.435 112.648C144.795 112.648 157.069 123.74 159.748 145.71L128.058 150.495C126.941 140.272 123.594 136.791 111.543 136.791C101.722 136.791 95.6969 139.403 95.6969 146.363C95.6969 155.717 102.615 157.021 117.791 159.631C137.654 162.894 161.534 167.897 161.534 194.652ZM359.912 153.968V159.84H396.737V153.968C396.737 142.656 392.272 136.782 378.658 136.782C364.599 136.782 359.912 143.745 359.912 153.968ZM427.756 197.692C425.525 215.094 415.705 232.497 378.658 232.497C341.612 232.497 328.668 212.267 328.668 188.122V156.799C328.668 132.002 341.389 112.424 378.658 112.424C416.374 112.424 427.756 131.783 427.756 160.932V181.814H359.911V191.384C359.911 201.39 363.26 209.003 378.658 209.003C393.834 209.003 395.843 203.565 397.182 193.124L427.756 197.692ZM489.816 232.5C526.639 232.5 541.814 213.576 541.814 188.125V156.803C541.814 130.918 526.639 112.427 489.816 112.427C452.991 112.427 438.263 130.918 438.263 156.803V188.125C438.263 213.576 452.768 232.5 489.816 232.5ZM489.809 207.263C476.864 207.263 470.393 200.52 470.393 189.644V156.146C470.393 145.053 476.864 138.31 489.809 138.31C502.753 138.31 509.671 145.053 509.671 156.146V189.644C509.671 200.52 502.753 207.263 489.809 207.263ZM585.219 145.079H602.443L633.841 113H584.479L553.082 145.079V229.534H585.219V145.079Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M154.079 12.5L110.98 57.5905H217.449L260.548 12.5H154.079Z" fill="#036DFF"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M61.7342 27.8827C62.6047 27.0112 63.7859 26.5215 65.0176 26.5215H205.047C206.924 26.5215 208.616 27.6515 209.334 29.3849C210.053 31.1183 209.657 33.1139 208.331 34.4415L167.954 74.8675C167.083 75.739 165.902 76.2287 164.67 76.2287H24.6408C22.7643 76.2287 21.0725 75.0986 20.3539 73.3652C19.6353 71.6319 20.0313 69.6363 21.3573 68.3086L61.7342 27.8827ZM66.9415 35.8027L35.8346 66.9474H162.747L193.853 35.8027H66.9415Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_229_18" x="16" y="12.5" width="621.841" height="228" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="2"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_229_18"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_229_18" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<filter id="filter1_d_229_18" x="0" y="0.5" width="653.841" height="260" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="8"/>
|
||||||
|
<feGaussianBlur stdDeviation="10"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_229_18"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_229_18" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
15
apps/slack/src/assets/saleor-logo.svg
Normal file
15
apps/slack/src/assets/saleor-logo.svg
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<svg width="105" height="36" viewBox="0 0 105 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_15056_2587" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="1" width="105" height="35">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1.30762H104.532V36H0V1.30762Z" fill="white"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_15056_2587)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3961 35.965C23.8159 35.965 25.4302 33.3727 25.4302 29.8214C25.4302 25.4534 21.5048 24.6367 18.2399 24.1039C15.7453 23.6778 14.608 23.4649 14.608 21.9378C14.608 20.8016 15.5985 20.3752 17.2127 20.3752C19.1937 20.3752 19.7439 20.9435 19.9275 22.6124L25.1367 21.8312C24.6963 18.2445 22.6787 16.4336 17.3594 16.4336C11.5266 16.4336 9.47225 18.7773 9.47225 22.5059C9.43568 27.3356 13.9845 27.8328 17.3594 28.4009C19.4503 28.7917 20.2577 29.1466 20.2577 30.5317C20.2577 31.6681 19.5607 32.1652 17.4695 32.1652C15.1584 32.1652 14.4614 31.7746 14.1679 29.8925L9.10547 30.6383C9.58231 34.4381 11.8201 35.965 17.3961 35.965Z" fill="#28234A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.1859 32.058C31.7278 32.058 30.8477 31.5254 30.8477 29.7497C30.8477 28.1516 31.7278 27.5125 34.3693 27.5125H37.0106V29.3591C37.0106 31.1346 36.1302 32.058 34.1859 32.058ZM31.9131 35.9646C34.7746 35.9646 36.0587 35.0412 36.9758 33.6917V35.716H42.1481V22.8605C42.1481 18.5637 40.4973 16.3975 34.7746 16.3975C29.2351 16.3975 27.291 17.9599 26.5205 21.7953L31.6564 22.5765C31.9865 20.943 32.4635 20.4457 34.5912 20.4457C36.6456 20.4457 36.9758 21.298 36.9758 22.7187V24.0327H34.5544C27.9879 24.0327 25.75 26.0212 25.75 30.0696C25.75 34.0468 27.7678 35.9646 31.9131 35.9646Z" fill="#28234A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.4688 35.7162H48.7515V8.31738L43.4688 13.5548V35.7162Z" fill="#28234A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.2111 24.1386V23.1799C55.2111 21.5109 55.9816 20.3742 58.2927 20.3742C60.5305 20.3742 61.2644 21.3333 61.2644 23.1799V24.1386H55.2111ZM58.2916 36.0001C64.3814 36.0001 65.9955 33.159 66.3623 30.3181L61.3366 29.5723C61.1164 31.2769 60.7862 32.1647 58.2916 32.1647C55.7605 32.1647 55.21 30.9218 55.21 29.2883V27.7259H66.3623V24.3167C66.3623 19.558 64.4913 16.3975 58.2916 16.3975C52.1653 16.3975 50.0742 19.5936 50.0742 23.642V28.7557C50.0742 32.6975 52.2019 36.0001 58.2916 36.0001Z" fill="#28234A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.1539 36.0002C82.2068 36.0002 84.7013 32.9107 84.7013 28.7558V23.6421C84.7013 19.4163 82.2068 16.3975 76.1539 16.3975C70.1007 16.3975 67.6797 19.4163 67.6797 23.6421V28.7558C67.6797 32.9107 70.0641 36.0002 76.1539 36.0002ZM76.1526 31.8806C74.0247 31.8806 72.9609 30.7799 72.9609 29.0042V23.5354C72.9609 21.7244 74.0247 20.6235 76.1526 20.6235C78.2803 20.6235 79.4174 21.7244 79.4174 23.5354V29.0042C79.4174 30.7799 78.2803 31.8806 76.1526 31.8806Z" fill="#28234A"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M94.1376 21.8172H91.3062V35.605H86.0236V21.8172H86.0234L91.1846 16.5801H99.2987L94.1376 21.8172Z" fill="#28234A"/>
|
||||||
|
</g>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8667 0L16.9141 7.2443H34.0895L41.0422 0H23.8667Z" fill="#8AC4EB"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.49792 2.99707L2.98438 9.49196H25.5738L32.0873 2.99707H9.49792Z" stroke="#28234A" stroke-width="1.2182" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
root: {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "280px auto 280px",
|
||||||
|
alignItems: "start",
|
||||||
|
gap: 32,
|
||||||
|
maxWidth: 1180,
|
||||||
|
margin: "0 auto",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = PropsWithChildren<{}>;
|
||||||
|
|
||||||
|
export const AppColumnsLayout = ({ children }: Props) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
return <div className={styles.root}>{children}</div>;
|
||||||
|
};
|
28
apps/slack/src/components/AppIcon/AppIcon.tsx
Normal file
28
apps/slack/src/components/AppIcon/AppIcon.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
appIconContainer: {
|
||||||
|
background: `rgb(95, 58, 199)`,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
borderRadius: "50%",
|
||||||
|
color: "#fff",
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AppIcon = () => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.appIconContainer}>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h2">S</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
52
apps/slack/src/components/MainBar/MainBar.tsx
Normal file
52
apps/slack/src/components/MainBar/MainBar.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Paper, PaperProps } from "@material-ui/core";
|
||||||
|
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
height: 96,
|
||||||
|
padding: "0 32px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
leftColumn: {
|
||||||
|
marginRight: "auto",
|
||||||
|
},
|
||||||
|
rightColumn: {},
|
||||||
|
iconColumn: {
|
||||||
|
marginRight: 24,
|
||||||
|
},
|
||||||
|
appName: { fontSize: 24, margin: 0 },
|
||||||
|
appAuthor: {
|
||||||
|
fontSize: 12,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontWeight: 500,
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: string;
|
||||||
|
author: string;
|
||||||
|
rightColumnContent?: ReactNode;
|
||||||
|
icon?: ReactNode;
|
||||||
|
} & PaperProps;
|
||||||
|
|
||||||
|
export const MainBar = ({ name, author, rightColumnContent, className, icon }: Props) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper elevation={0} className={clsx(styles.root, className)}>
|
||||||
|
{icon && <div className={styles.iconColumn}>{icon}</div>}
|
||||||
|
<div className={styles.leftColumn}>
|
||||||
|
<h1 className={styles.appName}>{name}</h1>
|
||||||
|
<h1 className={styles.appAuthor}>{author}</h1>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColumn}>{rightColumnContent}</div>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { GitHub, OfflineBoltOutlined } from "@material-ui/icons";
|
||||||
|
import { Button, makeStyles } from "@saleor/macaw-ui";
|
||||||
|
import { MainBar } from "../MainBar/MainBar";
|
||||||
|
import { useAppBridge, actions } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import { AppIcon } from "../AppIcon/AppIcon";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
buttonsGrid: { display: "flex", gap: 10 },
|
||||||
|
topBar: {
|
||||||
|
marginBottom: 32,
|
||||||
|
},
|
||||||
|
indexActions: {
|
||||||
|
marginTop: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SlackAppMainBar = () => {
|
||||||
|
const { appBridge } = useAppBridge();
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
const openInNewTab = (url: string) => {
|
||||||
|
appBridge?.dispatch(
|
||||||
|
actions.Redirect({
|
||||||
|
to: url,
|
||||||
|
newContext: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MainBar
|
||||||
|
icon={<AppIcon />}
|
||||||
|
className={styles.topBar}
|
||||||
|
name="Saleor Slack"
|
||||||
|
author="By Saleor Commerce"
|
||||||
|
rightColumnContent={
|
||||||
|
<div className={styles.buttonsGrid}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
startIcon={<GitHub />}
|
||||||
|
onClick={() => {
|
||||||
|
openInNewTab("https://github.com/saleor/saleor-app-slack");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Repository
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
startIcon={<OfflineBoltOutlined />}
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
openInNewTab("https://github.com/saleor/apps/discussions");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Request a feature
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
38
apps/slack/src/hooks/theme-synchronizer.tsx
Normal file
38
apps/slack/src/hooks/theme-synchronizer.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import { useTheme } from "@saleor/macaw-ui";
|
||||||
|
import { memo, useEffect } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macaw-ui stores its theme mode in memory and local storage. To synchronize App with Dashboard,
|
||||||
|
* Macaw must be informed about this change from AppBridge.
|
||||||
|
*
|
||||||
|
* If you are not using Macaw, you can remove this.
|
||||||
|
*/
|
||||||
|
function _ThemeSynchronizer() {
|
||||||
|
const { appBridgeState, appBridge } = useAppBridge();
|
||||||
|
const { setTheme, themeType } = useTheme();
|
||||||
|
|
||||||
|
// todo - replace this hook to appBridge.subscribe and react only only on initial theme event
|
||||||
|
// useEffect(() =>{
|
||||||
|
// appBridge?.subscribe('theme',console.log)
|
||||||
|
// },[appBridge])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!setTheme || !appBridgeState?.theme) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themeType !== appBridgeState?.theme) {
|
||||||
|
setTheme(appBridgeState.theme);
|
||||||
|
/**
|
||||||
|
* Hack to fix macaw, which is going into infinite loop on light mode (probably de-sync local storage with react state)
|
||||||
|
* TODO Fix me when Macaw 2.0 is shipped
|
||||||
|
*/
|
||||||
|
window.localStorage.setItem("macaw-ui-theme", appBridgeState.theme);
|
||||||
|
}
|
||||||
|
}, [appBridgeState?.theme, setTheme, themeType]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThemeSynchronizer = memo(_ThemeSynchronizer);
|
7
apps/slack/src/lib/is-in-iframe.ts
Normal file
7
apps/slack/src/lib/is-in-iframe.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export function isInIframe() {
|
||||||
|
try {
|
||||||
|
return window.self !== window.top;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import {
|
||||||
FetchAppDetailsQuery,
|
FetchAppDetailsQuery,
|
||||||
UpdateAppMetadataDocument,
|
UpdateAppMetadataDocument,
|
||||||
} from "../../generated/graphql";
|
} from "../../generated/graphql";
|
||||||
|
import { settingsManagerSecretKey } from "./saleor-app";
|
||||||
|
|
||||||
// Function is using urql graphql client to fetch all available metadata.
|
// 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
|
// Before returning query result, we are transforming response to list of objects with key and value fields
|
||||||
|
@ -54,7 +55,7 @@ export const createSettingsManager = (client: Client, appId: string) =>
|
||||||
// If your use case require plain text values, you can use MetadataManager.
|
// 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: "random key",
|
encryptionKey: settingsManagerSecretKey,
|
||||||
fetchMetadata: () => fetchAllMetadata(client),
|
fetchMetadata: () => fetchAllMetadata(client),
|
||||||
mutateMetadata: (metadata) => mutateMetadata(client, appId, metadata),
|
mutateMetadata: (metadata) => mutateMetadata(client, appId, metadata),
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,16 @@ 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") {
|
||||||
|
// throw new Error(
|
||||||
|
// "For production deployment SECRET_KEY is mandatory to use EncryptedSettingsManager."
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Use placeholder value for the development
|
||||||
|
export const settingsManagerSecretKey = process.env.SECRET_KEY || "CHANGE_ME";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prohibit installation from Saleors other than specified by the regex.
|
* Prohibit installation from Saleors other than specified by the regex.
|
||||||
* Regex source is ENV so if ENV is not set, all installations will be allowed.
|
* Regex source is ENV so if ENV is not set, all installations will be allowed.
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { ThemeProvider as MacawUIThemeProvider } from "@saleor/macaw-ui";
|
||||||
import React, { PropsWithChildren, useEffect } from "react";
|
import React, { PropsWithChildren, useEffect } from "react";
|
||||||
|
|
||||||
import { AppLayoutProps } from "../../types";
|
import { AppLayoutProps } from "../../types";
|
||||||
import { ThemeSynchronizer } from "@saleor/shared";
|
import { ThemeSynchronizer } from "../hooks/theme-synchronizer";
|
||||||
|
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
|
||||||
|
|
||||||
const themeOverrides: Partial<Theme> = {
|
const themeOverrides: Partial<Theme> = {
|
||||||
overrides: {
|
overrides: {
|
||||||
|
@ -29,11 +30,12 @@ const themeOverrides: Partial<Theme> = {
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
|
const appBridgeInstance =
|
||||||
|
typeof window !== "undefined" ? new AppBridge({ autoNotifyReady: false }) : undefined;
|
||||||
|
|
||||||
// That's a hack required by Macaw-UI incompatibility with React@18
|
// That's a hack required by Macaw-UI incompatibility with React@18
|
||||||
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
const ThemeProvider = MacawUIThemeProvider as React.FC<
|
||||||
PropsWithChildren<{ overrides: Partial<Theme>; ssr: boolean }>
|
PropsWithChildren<{ overrides: Partial<Theme>; ssr: boolean; defaultTheme?: "light" | "dark" }>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
function SaleorApp({ Component, pageProps }: AppLayoutProps) {
|
function SaleorApp({ Component, pageProps }: AppLayoutProps) {
|
||||||
|
@ -47,6 +49,7 @@ function SaleorApp({ Component, pageProps }: AppLayoutProps) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<NoSSRWrapper>
|
||||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||||
<ThemeProvider overrides={themeOverrides} ssr>
|
<ThemeProvider overrides={themeOverrides} ssr>
|
||||||
<ThemeSynchronizer />
|
<ThemeSynchronizer />
|
||||||
|
@ -54,6 +57,7 @@ function SaleorApp({ Component, pageProps }: AppLayoutProps) {
|
||||||
{getLayout(<Component {...pageProps} />)}
|
{getLayout(<Component {...pageProps} />)}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AppBridgeProvider>
|
</AppBridgeProvider>
|
||||||
|
</NoSSRWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ export default function Document() {
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="crossorigin" />
|
||||||
<Head />
|
<Head />
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;500;600;700;800&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;500;600;700;800&display=swap"
|
||||||
|
|
|
@ -54,4 +54,4 @@ export const handler = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createProtectedHandler(handler, saleorApp.apl);
|
export default createProtectedHandler(handler, saleorApp.apl, ["MANAGE_APPS", "MANAGE_ORDERS"]);
|
||||||
|
|
|
@ -20,6 +20,9 @@ import { ConfigurationError } from "../components/ConfigurationError/Configurati
|
||||||
import useAppApi from "../hooks/useAppApi";
|
import useAppApi from "../hooks/useAppApi";
|
||||||
import { saleorApp } from "../lib/saleor-app";
|
import { saleorApp } from "../lib/saleor-app";
|
||||||
import useDashboardNotifier from "../utils/useDashboardNotifier";
|
import useDashboardNotifier from "../utils/useDashboardNotifier";
|
||||||
|
import { Link } from "@material-ui/core";
|
||||||
|
import { SlackAppMainBar } from "../components/SlackAppMainBar/SlackAppMainBar";
|
||||||
|
import { AppColumnsLayout } from "../components/AppColumnsLayout/AppColumnsLayout";
|
||||||
|
|
||||||
interface ConfigurationField {
|
interface ConfigurationField {
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -133,6 +136,7 @@ function Configuration({ isVercel, appReady }: PageProps) {
|
||||||
<ConfirmButton
|
<ConfirmButton
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
fullWidth
|
||||||
transitionState={transitionState}
|
transitionState={transitionState}
|
||||||
labels={{
|
labels={{
|
||||||
confirm: "Save",
|
confirm: "Save",
|
||||||
|
@ -172,7 +176,7 @@ function Instructions() {
|
||||||
<Typography>How to configure</Typography>
|
<Typography>How to configure</Typography>
|
||||||
<List>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<a
|
<Link
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openExternalUrl(slackUrl.href);
|
openExternalUrl(slackUrl.href);
|
||||||
|
@ -180,7 +184,7 @@ function Instructions() {
|
||||||
href={slackUrl.href}
|
href={slackUrl.href}
|
||||||
>
|
>
|
||||||
Install Slack application
|
Install Slack application
|
||||||
</a>
|
</Link>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
Copy incoming Webhook URL from Slack app configuration and paste it below into
|
Copy incoming Webhook URL from Slack app configuration and paste it below into
|
||||||
|
@ -191,7 +195,7 @@ function Instructions() {
|
||||||
<Typography>Useful links</Typography>
|
<Typography>Useful links</Typography>
|
||||||
<List>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<a
|
<Link
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openExternalUrl("https://github.com/saleor/saleor-app-slack");
|
openExternalUrl("https://github.com/saleor/saleor-app-slack");
|
||||||
|
@ -199,10 +203,10 @@ function Instructions() {
|
||||||
href="https://github.com/saleor/saleor-app-slack"
|
href="https://github.com/saleor/saleor-app-slack"
|
||||||
>
|
>
|
||||||
Visit repository & readme
|
Visit repository & readme
|
||||||
</a>
|
</Link>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<a
|
<Link
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openExternalUrl("https://api.slack.com/messaging/webhooks");
|
openExternalUrl("https://api.slack.com/messaging/webhooks");
|
||||||
|
@ -210,7 +214,7 @@ function Instructions() {
|
||||||
href="https://api.slack.com/messaging/webhooks"
|
href="https://api.slack.com/messaging/webhooks"
|
||||||
>
|
>
|
||||||
Read about Slack apps that use incoming webhooks
|
Read about Slack apps that use incoming webhooks
|
||||||
</a>
|
</Link>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
|
@ -226,16 +230,20 @@ const ConfigurationWithAuth = withAuthorization({
|
||||||
|
|
||||||
ConfigurationWithAuth.getLayout = (page: ReactElement) => (
|
ConfigurationWithAuth.getLayout = (page: ReactElement) => (
|
||||||
<div>
|
<div>
|
||||||
|
<SlackAppMainBar />
|
||||||
|
<AppColumnsLayout>
|
||||||
|
<div />
|
||||||
|
<Card>
|
||||||
|
<CardHeader title="Configuration" />
|
||||||
|
<CardContent>{page}</CardContent>
|
||||||
|
</Card>
|
||||||
<Card style={{ marginBottom: 40 }}>
|
<Card style={{ marginBottom: 40 }}>
|
||||||
<CardHeader title="Instructions" />
|
<CardHeader title="Instructions" />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Instructions />
|
<Instructions />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
</AppColumnsLayout>
|
||||||
<CardHeader title="Configuration" />
|
|
||||||
<CardContent>{page}</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
141
apps/slack/src/pages/index.tsx
Normal file
141
apps/slack/src/pages/index.tsx
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import { NextPage } from "next";
|
||||||
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { FormEventHandler, useEffect } from "react";
|
||||||
|
import { useIsMounted } from "usehooks-ts";
|
||||||
|
import Image from "next/image";
|
||||||
|
import SaleorLogoImage from "../assets/saleor-logo.svg";
|
||||||
|
import SaleorLogoImageDark from "../assets/saleor-logo-dark.svg";
|
||||||
|
import { InputAdornment, LinearProgress, TextField, Typography } from "@material-ui/core";
|
||||||
|
import { Button, makeStyles, useTheme } from "@saleor/macaw-ui";
|
||||||
|
import { isInIframe } from "../lib/is-in-iframe";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
root: {
|
||||||
|
maxWidth: 960,
|
||||||
|
margin: "50px auto",
|
||||||
|
},
|
||||||
|
headline: {
|
||||||
|
marginTop: 70,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "50% 50%",
|
||||||
|
gap: 116,
|
||||||
|
marginTop: 48,
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
marginTop: 24,
|
||||||
|
},
|
||||||
|
submitButton: {
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
marginTop: 24,
|
||||||
|
"& > *": {
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Input = () => {
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
label="Your Saleor URL"
|
||||||
|
fullWidth
|
||||||
|
name="saleor-url"
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: <InputAdornment position="end">.saleor.cloud</InputAdornment>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common landing page in case of app being open outside the Dashboard.
|
||||||
|
* Allows quick installation with Saleor env input
|
||||||
|
*
|
||||||
|
* Can be safely removed
|
||||||
|
*/
|
||||||
|
const IndexPage: NextPage = () => {
|
||||||
|
const styles = useStyles();
|
||||||
|
const { appBridgeState } = useAppBridge();
|
||||||
|
const isMounted = useIsMounted();
|
||||||
|
const { replace } = useRouter();
|
||||||
|
const { themeType } = useTheme();
|
||||||
|
|
||||||
|
const onFormSubmit: FormEventHandler<HTMLFormElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const appOrigin = window.location.origin;
|
||||||
|
const appManifestUrl = new URL("api/manifest", appOrigin);
|
||||||
|
const saleorUrlSlug = new FormData(e.currentTarget).get("saleor-url") as string;
|
||||||
|
const saleorUrl = new URL("https://" + saleorUrlSlug.replace("https://", "") + ".saleor.cloud");
|
||||||
|
|
||||||
|
const installationUrl = new URL(
|
||||||
|
`dashboard/apps/install?manifestUrl=${appManifestUrl}`,
|
||||||
|
saleorUrl
|
||||||
|
).href;
|
||||||
|
|
||||||
|
window.location.href = installationUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMounted() && appBridgeState?.ready) {
|
||||||
|
replace("/configuration");
|
||||||
|
}
|
||||||
|
}, [isMounted, appBridgeState?.ready]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Check also some timeout and show error if appBridge never handshakes
|
||||||
|
*/
|
||||||
|
if (isInIframe()) {
|
||||||
|
return <LinearProgress />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<Image
|
||||||
|
alt="Saleor logo"
|
||||||
|
width={200}
|
||||||
|
src={themeType === "light" ? SaleorLogoImage : SaleorLogoImageDark}
|
||||||
|
/>
|
||||||
|
<Typography className={styles.headline} variant="h1">
|
||||||
|
The Slack App has to be <br />
|
||||||
|
launched in the Saleor Dashboard
|
||||||
|
</Typography>
|
||||||
|
<div className={styles.grid}>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h3">
|
||||||
|
Provide you Saleor URL
|
||||||
|
<br /> to quickly install the app
|
||||||
|
</Typography>
|
||||||
|
<form onSubmit={onFormSubmit} className={styles.form}>
|
||||||
|
<Input />
|
||||||
|
<Button type="submit" className={styles.submitButton} fullWidth variant="primary">
|
||||||
|
{" "}
|
||||||
|
Submit and start installation
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography variant="h3">
|
||||||
|
Or check the instructions
|
||||||
|
<br /> and see how to install the app
|
||||||
|
</Typography>
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<Button variant="secondary" fullWidth>
|
||||||
|
Open repository
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" fullWidth>
|
||||||
|
See Saleor Docs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IndexPage;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue