Compare commits
77 commits
taxes/docu
...
main
Author | SHA1 | Date | |
---|---|---|---|
4b12982597 | |||
292a5bdb0c | |||
![]() |
653b98df86 | ||
![]() |
ae6dbb125b | ||
![]() |
6948fe41ca | ||
![]() |
2a22cf47f2 | ||
![]() |
927d2b3bb6 | ||
![]() |
5dee65ad2c | ||
![]() |
c6db32b40e | ||
![]() |
5d3d81d5c2 | ||
![]() |
52c46987f3 | ||
![]() |
1982d81f17 | ||
![]() |
767d0be722 | ||
![]() |
2951fb3ab6 | ||
![]() |
7e0755ec9e | ||
![]() |
a32fe7caf4 | ||
![]() |
4a2275e999 | ||
![]() |
a5f226bdef | ||
![]() |
a0a607ce99 | ||
![]() |
6ac7799d72 | ||
![]() |
4aee4e11f8 | ||
![]() |
86bc946b3e | ||
![]() |
1033b93747 | ||
![]() |
2e296996cb | ||
![]() |
068e529cfe | ||
![]() |
3bfcfa5b01 | ||
![]() |
6688a55102 | ||
![]() |
4efebde36d | ||
![]() |
9f76a2f1f8 | ||
![]() |
627712bef3 | ||
![]() |
8e4941fd2a | ||
![]() |
55315b89f3 | ||
![]() |
30140eea06 | ||
![]() |
e8660e8bb9 | ||
![]() |
ceddcf96eb | ||
![]() |
1e3c08c029 | ||
![]() |
23e71bc7a2 | ||
![]() |
261e9d1a0c | ||
![]() |
0aa1d12cc4 | ||
![]() |
ed30a818e3 | ||
![]() |
056209486c | ||
![]() |
0f84985c98 | ||
![]() |
2a1385bab1 | ||
![]() |
a81f061fcf | ||
![]() |
d9d0f64a01 | ||
![]() |
fcc37e7c7e | ||
![]() |
254cd4c9a3 | ||
![]() |
17925f2115 | ||
![]() |
e9378e7af7 | ||
![]() |
34efd39dcf | ||
![]() |
6f1c5c9436 | ||
![]() |
45ed9fb444 | ||
![]() |
4a635620c4 | ||
![]() |
783bd5ec55 | ||
![]() |
3bd2d33d97 | ||
![]() |
6ea3e38bd8 | ||
![]() |
be761b251e | ||
![]() |
c50797e836 | ||
![]() |
8b3d9617e6 | ||
![]() |
a2cac7849b | ||
![]() |
895004dd55 | ||
![]() |
7d574c70c9 | ||
![]() |
69fe973121 | ||
![]() |
3002354c5e | ||
![]() |
7f0d8d24a3 | ||
![]() |
bb504d95aa | ||
![]() |
7b19ab44c3 | ||
![]() |
416c92fb6c | ||
![]() |
c7b3d35f5d | ||
![]() |
b8c528397a | ||
![]() |
fe767a494b | ||
![]() |
a951af938e | ||
![]() |
6a84b4b7e0 | ||
![]() |
0b0297eeb8 | ||
![]() |
261957fda4 | ||
![]() |
fc5e6396d2 | ||
![]() |
aece07338e |
511 changed files with 118082 additions and 6144 deletions
8
.changeset/pretty-pink-panda.md
Normal file
8
.changeset/pretty-pink-panda.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#changelog
|
||||||
|
---
|
||||||
|
"apps": minor
|
||||||
|
---
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `apps/emails-and-messages/.env.template`: Described the new environment variable and how it works
|
||||||
|
- `apps/emails-and-messages/src/saleor-app.ts`: Added case "redis" for switch(AplType), which takes advantage of the [RedisAPL PR](https://github.com/saleor/app-sdk/pull/287) I submitted
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -8,5 +8,4 @@
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] `.github/dependabot.yaml` is up-to date.
|
|
||||||
- [ ] I added changesets and [read good practices](/.changeset/README.md).
|
- [ ] I added changesets and [read good practices](/.changeset/README.md).
|
||||||
|
|
112
.github/dependabot.yaml
vendored
112
.github/dependabot.yaml
vendored
|
@ -2,110 +2,18 @@ version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "[skip ci]"
|
prefix: "[skip ci]"
|
||||||
|
groups:
|
||||||
|
trpc:
|
||||||
|
patterns:
|
||||||
|
- "@trpc/*"
|
||||||
|
codegen:
|
||||||
|
patterns:
|
||||||
|
- "@graphql-codegen/*"
|
||||||
|
rtl:
|
||||||
|
patterns:
|
||||||
|
- "@testing-library/*"
|
||||||
|
|
||||||
# Packages
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/packages/eslint-config-saleor"
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/packages/shared"
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/packages/ui"
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/packages/react-hook-form-macaw"
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
|
|
||||||
# Apps
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/apps/cms-v2"
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/crm
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/data-importer
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/emails-and-messages
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/invoices
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/klaviyo
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/products-feed
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/search
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: apps/slack
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
open-pull-requests-limit: 1
|
|
||||||
directory: apps/taxes
|
|
||||||
commit-message:
|
|
||||||
prefix: "[skip ci]"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
|
|
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
|
@ -6,5 +6,6 @@
|
||||||
"App: Slack": "apps/slack/**/*"
|
"App: Slack": "apps/slack/**/*"
|
||||||
"App: Taxes": "apps/taxes/**/*"
|
"App: Taxes": "apps/taxes/**/*"
|
||||||
"App: Emails & Messages": "apps/emails-and-messages/**/*"
|
"App: Emails & Messages": "apps/emails-and-messages/**/*"
|
||||||
"App: CMS": ["apps/cms-v2/**/*"]
|
"App: CMS": "apps/cms-v2/**/*"
|
||||||
"App: CRM": "apps/crm/**/*"
|
"App: CRM": "apps/crm/**/*"
|
||||||
|
"App: Segment": "apps/segment/**/*"
|
||||||
|
|
9
.github/workflows/assign-pr.yml
vendored
9
.github/workflows/assign-pr.yml
vendored
|
@ -6,9 +6,12 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
assign_creator:
|
assign_creator:
|
||||||
|
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Assign PR to creator
|
- name: Assign PR to creator
|
||||||
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
|
env:
|
||||||
with:
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||||
|
CREATOR: ${{ github.event.pull_request.user.login }}
|
||||||
|
run: gh pr edit "$PR_URL" --add-assignee "$CREATOR"
|
||||||
|
|
31
.github/workflows/changeset-checker.yml
vendored
Normal file
31
.github/workflows/changeset-checker.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
name: Changesets
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- labeled
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
branches-ignore:
|
||||||
|
- 'changeset-release/**'
|
||||||
|
jobs:
|
||||||
|
changeset_check:
|
||||||
|
name: Changeset added to the PR
|
||||||
|
# Adding 'skip changesets' label to the PR will skip this job
|
||||||
|
if: ${{ !contains( github.event.pull_request.labels.*.name, 'skip changeset') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
# check out full history
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: "pnpm"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
- name: Changeset added
|
||||||
|
run: npx changeset status --since origin/main
|
2
.github/workflows/check-deps.yml
vendored
2
.github/workflows/check-deps.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- uses: JamieMason/syncpack-github-action@0.2.2
|
- uses: JamieMason/syncpack-github-action@c145cec44b3731b3fe8e859679e240d6ae011f0f
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
package-manager: "pnpm"
|
package-manager: "pnpm"
|
||||||
|
|
10
.github/workflows/check-spelling.yml
vendored
Normal file
10
.github/workflows/check-spelling.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
name: "Check spelling"
|
||||||
|
on: # rebuild any PRs and main branch changes
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
spellcheck: # run the action
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: streetsidesoftware/cspell-action@22e32eb3d70acf30e3fc09bd46edc1d30fb2d6db
|
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
|
@ -25,11 +25,11 @@ jobs:
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- uses: pnpm/action-setup@v2
|
- uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
- run: pnpm install
|
- run: pnpm install
|
||||||
- name: Create Release Pull Request
|
- name: Create Release Pull Request
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@f13b1baaa620fde937751f5d2c3572b9da32af23
|
||||||
id: changesets
|
id: changesets
|
||||||
with:
|
with:
|
||||||
title: 🚀 Release apps
|
title: 🚀 Release apps
|
||||||
|
|
2
.github/workflows/unit-tests.yml
vendored
2
.github/workflows/unit-tests.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: pnpm/action-setup@v2.2.4
|
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -43,3 +43,4 @@ apps/**/generated
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.cspellcache
|
|
@ -1 +1 @@
|
||||||
* @saleor/appstore
|
* @saleor/delivery-engineering-js
|
||||||
|
|
11
README.md
11
README.md
|
@ -48,10 +48,10 @@ This repository serves as a starting point in the exploration of Saleor apps.
|
||||||
In the `apps` folder, you will find the following applications:
|
In the `apps` folder, you will find the following applications:
|
||||||
|
|
||||||
- [crm](https://docs.saleor.io/docs/3.x/developer/app-store/apps/crm) - exports customers from Saleor to CRM.
|
- [crm](https://docs.saleor.io/docs/3.x/developer/app-store/apps/crm) - exports customers from Saleor to CRM.
|
||||||
- [cms](./apps/cms) - exports products from Saleor to CMS.
|
- [cms](https://docs.saleor.io/docs/3.x/developer/app-store/apps/cms) - exports products from Saleor to CMS.
|
||||||
- [data-importer](./apps/data-importer) - import data from CSV to Saleor.
|
- [data-importer](./apps/data-importer) - import data from CSV to Saleor.
|
||||||
- [emails-and-messages](./apps/emails-and-messages) - notifications and email communication with customers.
|
- [emails-and-messages](https://docs.saleor.io/docs/3.x/developer/app-store/apps/emails-and-messages/overview) - notifications and email communication with customers.
|
||||||
- [invoices](./apps/invoices) - generate invoice PDF for each order.
|
- [invoices](https://docs.saleor.io/docs/3.x/developer/app-store/apps/invoices) - generate invoice PDF for each order.
|
||||||
- [klaviyo](./apps/klaviyo) - send Saleor events to Klaviyo, where you can notify the customers.
|
- [klaviyo](./apps/klaviyo) - send Saleor events to Klaviyo, where you can notify the customers.
|
||||||
- [monitoring](./apps/monitoring) - send Saleor logs to 3rd party Monitoring services
|
- [monitoring](./apps/monitoring) - send Saleor logs to 3rd party Monitoring services
|
||||||
- [products-feed](./apps/products-feed) - generate products feed XML
|
- [products-feed](./apps/products-feed) - generate products feed XML
|
||||||
|
@ -59,6 +59,11 @@ In the `apps` folder, you will find the following applications:
|
||||||
- [slack](./apps/slack) - get notifications on Slack channel from Saleor events.
|
- [slack](./apps/slack) - get notifications on Slack channel from Saleor events.
|
||||||
- [taxes](https://docs.saleor.io/docs/3.x/developer/app-store/apps/taxes) - calculate order and checkout taxes using external services.
|
- [taxes](https://docs.saleor.io/docs/3.x/developer/app-store/apps/taxes) - calculate order and checkout taxes using external services.
|
||||||
|
|
||||||
|
## Another official apps
|
||||||
|
|
||||||
|
Some of Saleor apps are available in separate repositories:
|
||||||
|
- [Stripe](https://github.com/saleor/saleor-app-payment-stripe)
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
You can find the documentation for saleor/apps on [docs.saleor.io](https://docs.saleor.io/docs/3.x/developer/app-store/development).
|
You can find the documentation for saleor/apps on [docs.saleor.io](https://docs.saleor.io/docs/3.x/developer/app-store/development).
|
||||||
|
|
|
@ -1,5 +1,88 @@
|
||||||
# saleor-app-cms-v2
|
# saleor-app-cms-v2
|
||||||
|
|
||||||
|
## 2.3.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||||
|
- 5dee65a: Updated dependencies:
|
||||||
|
- @graphql-codegen/cli@5.0.0
|
||||||
|
- 2e29699: Updated Sentry package
|
||||||
|
|
||||||
|
## 2.3.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- e8660e8: Extracted UI components and use shared package
|
||||||
|
- 30140ee: Improved some text typos.
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- @saleor/apps-shared@1.9.0
|
||||||
|
- @saleor/apps-ui@1.2.0
|
||||||
|
|
||||||
|
## 2.3.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a81f061: Updated Macaw to pre-127
|
||||||
|
- d9d0f64: Fixed background color of the modal. Previously white background was missing and only blur was applied. Now it is blurred and white with an opacity (in the dark mode it is fixed with dark grey)
|
||||||
|
- fcc37e7: Remove clsx package from the projects no longer using it.
|
||||||
|
- Updated dependencies [2a1385b]
|
||||||
|
- Updated dependencies [a81f061]
|
||||||
|
- Updated dependencies [fcc37e7]
|
||||||
|
- @saleor/apps-shared@1.8.1
|
||||||
|
- @saleor/react-hook-form-macaw@0.2.5
|
||||||
|
- @saleor/apps-ui@1.1.8
|
||||||
|
|
||||||
|
## 2.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 6f1c5c9: Added Payload CMS support.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- e9378e7: Fix PayloadCMS form where two inputs were stuck together without a margin. Now, they are placed in two columns
|
||||||
|
- 6f1c5c9: Fix styling of modal in the dark mode
|
||||||
|
|
||||||
|
## 2.2.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8b3d961: Updated Macaw UI to pre.118
|
||||||
|
- c50797e: Extracted MetadataManager creation to factory from shared package
|
||||||
|
- Updated dependencies [8b3d961]
|
||||||
|
- Updated dependencies [c50797e]
|
||||||
|
- @saleor/react-hook-form-macaw@0.2.4
|
||||||
|
- @saleor/apps-shared@1.8.0
|
||||||
|
- @saleor/apps-ui@1.1.7
|
||||||
|
|
||||||
|
## 2.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3002354: Added error logging for exceptions thrown at tRPC routes.
|
||||||
|
|
||||||
|
## 2.2.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- fe767a4: Changed public (manifest) name app to be "CMS" (previously it was "CMS 2", since two apps existed at the same time)
|
||||||
|
|
||||||
|
## 2.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 261957f: Updated dependencies: vite, vitest, eslint, prettier, dotenv, turbo, syncpack, changesets, lint staged
|
||||||
|
- Updated dependencies [261957f]
|
||||||
|
- @saleor/react-hook-form-macaw@0.2.3
|
||||||
|
- @saleor/apps-shared@1.7.6
|
||||||
|
- @saleor/apps-ui@1.1.6
|
||||||
|
|
||||||
## 2.1.1
|
## 2.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "saleor-app-cms-v2",
|
"name": "saleor-app-cms-v2",
|
||||||
"version": "2.1.1",
|
"version": "2.3.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm generate && next build",
|
"build": "pnpm generate && next build",
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||||
|
@ -13,20 +13,20 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@datocms/cma-client-browser": "2.0.0",
|
"@datocms/cma-client-browser": "2.0.0",
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.3.1",
|
||||||
"@saleor/app-sdk": "0.41.1",
|
"@saleor/app-sdk": "0.43.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.67.0",
|
||||||
"@tanstack/react-query": "^4.29.19",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
"@trpc/client": "10.34.0",
|
"@trpc/client": "10.38.1",
|
||||||
"@trpc/next": "10.34.0",
|
"@trpc/next": "10.38.1",
|
||||||
"@trpc/react-query": "10.34.0",
|
"@trpc/react-query": "10.38.1",
|
||||||
"@trpc/server": "10.34.0",
|
"@trpc/server": "10.38.1",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.4",
|
||||||
"contentful-management": "10.38.3",
|
"contentful-management": "10.38.3",
|
||||||
"graphql": "16.7.1",
|
"graphql": "16.7.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
"p-ratelimit": "1.0.1",
|
"p-ratelimit": "1.0.1",
|
||||||
"pino": "^8.14.1",
|
"pino": "^8.14.1",
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
|
"qs": "6.11.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "4.0.10",
|
"react-error-boundary": "4.0.10",
|
||||||
|
@ -42,23 +43,24 @@
|
||||||
"strapi-sdk-js": "2.2.0",
|
"strapi-sdk-js": "2.2.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.4.8",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.34.1",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "4.0.1",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
"@graphql-codegen/typescript": "4.0.1",
|
"@graphql-codegen/typescript": "4.0.1",
|
||||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
|
"@types/qs": "^6.9.7",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"node-mocks-http": "^1.12.2",
|
"node-mocks-http": "^1.12.2",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
|
|
|
@ -6,26 +6,20 @@ import { Select } from "@saleor/react-hook-form-macaw";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ButtonsBox } from "../ui/buttons-box";
|
|
||||||
import { ProvidersResolver } from "../providers/providers-resolver";
|
import { ProvidersResolver } from "../providers/providers-resolver";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
connectionId: z.string().min(7),
|
connectionId: z.string().min(7),
|
||||||
});
|
});
|
||||||
|
|
||||||
const EmptyState = () => (
|
const EmptyState = () => (
|
||||||
<Box
|
<Layout.AppSectionCard>
|
||||||
display="flex"
|
<Box display="flex" flexDirection={"column"} gap={4} justifyContent={"center"}>
|
||||||
paddingY={4}
|
<Text variant="heading">Bulk products synchronization</Text>
|
||||||
flexDirection={"column"}
|
<Text>Create a channel connection above to enable bulk synchronization.</Text>
|
||||||
gap={4}
|
</Box>
|
||||||
alignItems={"center"}
|
</Layout.AppSectionCard>
|
||||||
justifyContent={"center"}
|
|
||||||
>
|
|
||||||
<Text variant="heading">No connections configured</Text>
|
|
||||||
<Text>Create a channel connection above to enable bulk synchronization.</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const BulkSyncSection = () => {
|
export const BulkSyncSection = () => {
|
||||||
|
@ -42,7 +36,7 @@ export const BulkSyncSection = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!connections || !providers) {
|
if (!connections || !providers) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connections.length === 0) {
|
if (connections.length === 0) {
|
||||||
|
@ -50,7 +44,7 @@ export const BulkSyncSection = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Layout.AppSectionCard>
|
||||||
<Text as="h2" marginBottom={6} variant="heading">
|
<Text as="h2" marginBottom={6} variant="heading">
|
||||||
Bulk products synchronization
|
Bulk products synchronization
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -88,6 +82,6 @@ export const BulkSyncSection = () => {
|
||||||
<Button type="submit">Start sync</Button>
|
<Button type="submit">Start sync</Button>
|
||||||
</ButtonsBox>
|
</ButtonsBox>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Layout.AppSectionCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
import { Breadcrumbs, ButtonsBox, Layout } from "@saleor/apps-ui";
|
||||||
import { ArrowRightIcon, Box, Button, Text } from "@saleor/macaw-ui/next";
|
import { ArrowRightIcon, Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { ChannelProviderConnectionConfig, ProvidersConfig } from "../configuration";
|
import { ChannelProviderConnectionConfig, ProvidersConfig } from "../configuration";
|
||||||
import { AppHeader } from "../ui/app-header";
|
import { AppHeader } from "../ui/app-header";
|
||||||
import { AppSection } from "../ui/app-section";
|
|
||||||
import { ButtonsBox } from "../ui/buttons-box";
|
|
||||||
|
|
||||||
import { useBulkSyncProductsState } from "./use-bulk-sync-products-state";
|
import { useBulkSyncProductsState } from "./use-bulk-sync-products-state";
|
||||||
import { useFetchAllProducts } from "./use-fetch-all-products";
|
import { useFetchAllProducts } from "./use-fetch-all-products";
|
||||||
|
@ -14,16 +12,19 @@ import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
|
||||||
const FetchProductsStep = (props: { onButtonClick(): void }) => {
|
const FetchProductsStep = (props: { onButtonClick(): void }) => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Layout.AppSectionCard
|
||||||
|
footer={
|
||||||
|
<ButtonsBox>
|
||||||
|
<Button onClick={props.onButtonClick}>Prefetch products</Button>
|
||||||
|
</ButtonsBox>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Text variant="heading" as="h2" marginBottom={4}>
|
<Text variant="heading" as="h2" marginBottom={4}>
|
||||||
Saleor products fetch
|
Saleor products fetch
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="p">Click the button to start fetching products from Saleor API</Text>
|
<Text as="p">Click the button to start fetching products from Saleor API</Text>
|
||||||
<Text as="p">After products are fetched, you will be able to upload them to the CMS</Text>
|
<Text as="p">After products are fetched, you will be able to upload them to the CMS</Text>
|
||||||
<ButtonsBox>
|
</Layout.AppSectionCard>
|
||||||
<Button onClick={props.onButtonClick}>Prefetch products</Button>
|
|
||||||
</ButtonsBox>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ export const BulkSyncView = ({
|
||||||
|
|
||||||
const { products, finished: saleorProductsFetchFinished } = useFetchAllProducts(
|
const { products, finished: saleorProductsFetchFinished } = useFetchAllProducts(
|
||||||
state === "fetching",
|
state === "fetching",
|
||||||
connection.channelSlug
|
connection.channelSlug,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { productsStatusList, setInitialProducts, setItemStatus, finished } =
|
const { productsStatusList, setInitialProducts, setItemStatus, finished } =
|
||||||
|
@ -121,9 +122,14 @@ export const BulkSyncView = ({
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
marginBottom={8}
|
marginBottom={8}
|
||||||
mainContent={(() => {
|
heading="1. Fetch products"
|
||||||
|
sideContent={
|
||||||
|
<Text>First pre-fetch all Product Variants from Saleor. Do not close the app.</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(() => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "initial": {
|
case "initial": {
|
||||||
return (
|
return (
|
||||||
|
@ -149,33 +155,29 @@ export const BulkSyncView = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
heading="1. Fetch products"
|
</Layout.AppSection>
|
||||||
sideContent={
|
|
||||||
<Text>First pre-fetch all Product Variants from Saleor. Do not close the app.</Text>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{(state === "fetched" || state === "uploading") && productsStatusList && (
|
{(state === "fetched" || state === "uploading") && productsStatusList && (
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
|
marginTop={14}
|
||||||
heading="2. Upload to the CMS"
|
heading="2. Upload to the CMS"
|
||||||
sideContent={<Text>Send listed variants to the CMS</Text>}
|
sideContent={<Text>Send listed variants to the CMS</Text>}
|
||||||
mainContent={
|
>
|
||||||
<Box>
|
<Layout.AppSectionCard>
|
||||||
<Text as="h2" marginBottom={4} variant="heading">
|
<Text as="h2" marginBottom={4} variant="heading">
|
||||||
Upload products
|
Upload products
|
||||||
</Text>
|
</Text>
|
||||||
{state === "fetched" && (
|
{state === "fetched" && (
|
||||||
<Box marginBottom={4}>
|
<Box marginBottom={4}>
|
||||||
<Text as="p" marginBottom={2}>
|
<Text as="p" marginBottom={2}>
|
||||||
Verify products below and click the button to start uploading.
|
Verify products below and click the button to start uploading.
|
||||||
</Text>
|
</Text>
|
||||||
<Button onClick={() => setState("uploading")}>Start uploading</Button>
|
<Button onClick={() => setState("uploading")}>Start uploading</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<VariantsSyncStatusList marginTop={8} variants={productsStatusList} />
|
<VariantsSyncStatusList marginTop={8} variants={productsStatusList} />
|
||||||
</Box>
|
</Layout.AppSectionCard>
|
||||||
}
|
</Layout.AppSection>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Button, Text } from "@saleor/macaw-ui/next";
|
import { Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import { ButtonsBox } from "../ui/buttons-box";
|
|
||||||
import { Modal } from "../ui/modal";
|
import { Modal } from "../ui/modal";
|
||||||
import {
|
import {
|
||||||
AddConnectionForm,
|
AddConnectionForm,
|
||||||
|
@ -7,7 +6,7 @@ import {
|
||||||
AddConnectionFormSchema,
|
AddConnectionFormSchema,
|
||||||
} from "./add-connection-form";
|
} from "./add-connection-form";
|
||||||
import { trpcClient } from "../trpc/trpc-client";
|
import { trpcClient } from "../trpc/trpc-client";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { ButtonsBox, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
const defaultValues: AddConnectionFormSchema = { channelSlug: "", providerId: "" };
|
const defaultValues: AddConnectionFormSchema = { channelSlug: "", providerId: "" };
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ export const AddConnectionModal = (props: { onSuccess(): void; onClose(): void }
|
||||||
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
||||||
|
|
||||||
if (!providers) {
|
if (!providers) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mutateAsync: addProviderMutate, isLoading } =
|
const { mutateAsync: addProviderMutate, isLoading } =
|
||||||
|
|
|
@ -1,35 +1,31 @@
|
||||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { trpcClient } from "../trpc/trpc-client";
|
import { trpcClient } from "../trpc/trpc-client";
|
||||||
import { ButtonsBox } from "../ui/buttons-box";
|
|
||||||
import { AddConnectionFormSchema } from "./add-connection-form";
|
|
||||||
import { AddConnectionModal } from "./add-connection-modal";
|
import { AddConnectionModal } from "./add-connection-modal";
|
||||||
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
|
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
|
||||||
import { ConnectionsList } from "./connections-list";
|
import { ConnectionsList } from "./connections-list";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
|
||||||
|
|
||||||
const NoConnections = (props: { onCreate(): void; enabled: boolean }) => (
|
const NoConnections = (props: { onCreate(): void; enabled: boolean }) => (
|
||||||
<Box>
|
<Box>
|
||||||
<ChanelProviderConnectionsSectionHeader />
|
<ChanelProviderConnectionsSectionHeader />
|
||||||
<Text marginBottom={4} as="p">
|
<Text as="p">
|
||||||
No channels connected yet.{" "}
|
No channels connected yet.{" "}
|
||||||
{!props.enabled &&
|
{!props.enabled &&
|
||||||
"Ensure you have created a provider configuration that can be connected first."}
|
"Ensure you have created a provider configuration that can be connected first."}
|
||||||
</Text>
|
</Text>
|
||||||
{props.enabled && (
|
|
||||||
<ButtonsBox>
|
|
||||||
<Button onClick={props.onCreate}>Create first connection</Button>
|
|
||||||
</ButtonsBox>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ChannelProviderConnectionList = () => {
|
export const ChannelProviderConnectionList = () => {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
|
||||||
const { data: connectionsData, refetch: refetchConnections } =
|
const {
|
||||||
trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
data: connectionsData,
|
||||||
|
refetch: refetchConnections,
|
||||||
|
isLoading,
|
||||||
|
} = trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
||||||
|
|
||||||
const { mutate: removeConnection } =
|
const { mutate: removeConnection } =
|
||||||
trpcClient.channelsProvidersConnection.removeConnection.useMutation({
|
trpcClient.channelsProvidersConnection.removeConnection.useMutation({
|
||||||
|
@ -45,19 +41,33 @@ export const ChannelProviderConnectionList = () => {
|
||||||
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
||||||
|
|
||||||
if (!providers) {
|
if (!providers) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = (connectionId: string) => {
|
const handleDelete = (connectionId: string) => {
|
||||||
removeConnection({ id: connectionId });
|
removeConnection({ id: connectionId });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!connectionsData) {
|
if (isLoading || !connectionsData) {
|
||||||
return <Text>Loading</Text>;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Layout.AppSectionCard
|
||||||
|
footer={
|
||||||
|
providers.length > 0 && (
|
||||||
|
<ButtonsBox>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setDialogOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add connection
|
||||||
|
</Button>
|
||||||
|
</ButtonsBox>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
{dialogOpen && (
|
{dialogOpen && (
|
||||||
<AddConnectionModal
|
<AddConnectionModal
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
@ -79,17 +89,6 @@ export const ChannelProviderConnectionList = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{connectionsData.length > 0 && <ConnectionsList onRemove={handleDelete} />}
|
{connectionsData.length > 0 && <ConnectionsList onRemove={handleDelete} />}
|
||||||
{connectionsData.length > 0 && (
|
</Layout.AppSectionCard>
|
||||||
<ButtonsBox marginTop={6}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setDialogOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add connection
|
|
||||||
</Button>
|
|
||||||
</ButtonsBox>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React from "react";
|
||||||
import { trpcClient } from "../trpc/trpc-client";
|
import { trpcClient } from "../trpc/trpc-client";
|
||||||
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
|
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
|
||||||
import { ProvidersResolver } from "../providers/providers-resolver";
|
import { ProvidersResolver } from "../providers/providers-resolver";
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
export const ConnectionsList = (props: { onRemove(connectionId: string): void }) => {
|
export const ConnectionsList = (props: { onRemove(connectionId: string): void }) => {
|
||||||
const { data } = trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
const { data } = trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
|
||||||
|
@ -12,7 +12,7 @@ export const ConnectionsList = (props: { onRemove(connectionId: string): void })
|
||||||
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
|
||||||
|
|
||||||
if (!data || !providers) {
|
if (!data || !providers) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,101 +1,12 @@
|
||||||
import {
|
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
MetadataEntry,
|
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||||
EncryptedMetadataManager,
|
import { Client } from "urql";
|
||||||
SettingsManager,
|
|
||||||
} 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
gql`
|
|
||||||
mutation RemoveMetadata($id: ID!, $keys: [String!]!) {
|
|
||||||
deletePrivateMetadata(id: $id, keys: $keys) {
|
|
||||||
errors {
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export type SimpleGraphqlClient = Pick<Client, "mutation" | "query">;
|
|
||||||
|
|
||||||
async function fetchAllMetadata(client: SimpleGraphqlClient): 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 })) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mutateMetadata(
|
|
||||||
client: SimpleGraphqlClient,
|
|
||||||
metadata: MetadataEntry[],
|
|
||||||
appId: string
|
|
||||||
) {
|
|
||||||
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 = (
|
export const createSettingsManager = (
|
||||||
client: SimpleGraphqlClient,
|
client: Pick<Client, "query" | "mutation">,
|
||||||
appId: string
|
appId: string,
|
||||||
): SettingsManager => {
|
): SettingsManager => {
|
||||||
/*
|
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||||
* EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
|
|
||||||
* We recommend it for production, because all values are encrypted.
|
return metadataManagerFactory.create(client, appId);
|
||||||
* 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, appId),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { PayloadCMS } from "@/modules/providers/payloadcms/payloadcms";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { SaleorProviderFieldsMappingSchema } from "./saleor-provider-fields-mapping.schema";
|
||||||
|
|
||||||
|
const InputSchema = z.object({
|
||||||
|
type: z.literal(PayloadCMS.type),
|
||||||
|
authToken: z.string(),
|
||||||
|
configName: z.string().min(1),
|
||||||
|
collectionName: z.string().min(1),
|
||||||
|
productVariantFieldsMapping: SaleorProviderFieldsMappingSchema,
|
||||||
|
payloadApiUrl: z.string().url(),
|
||||||
|
authenticatedUserSlug: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const FullSchema = InputSchema.extend({
|
||||||
|
id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export namespace PayloadCmsProviderConfig {
|
||||||
|
export type InputShape = z.infer<typeof InputSchema>;
|
||||||
|
export type FullShape = z.infer<typeof FullSchema>;
|
||||||
|
|
||||||
|
export const Schema = {
|
||||||
|
Input: InputSchema,
|
||||||
|
Full: FullSchema,
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import { ContentfulProviderConfig } from "./contentful-provider.schema";
|
||||||
import { BuilderIoProviderConfig } from "./builder-provider.schema";
|
import { BuilderIoProviderConfig } from "./builder-provider.schema";
|
||||||
import { StrapiProviderConfig } from "./strapi-provider.schema";
|
import { StrapiProviderConfig } from "./strapi-provider.schema";
|
||||||
import { DatocmsProviderConfig } from "./datocms-provider.schema";
|
import { DatocmsProviderConfig } from "./datocms-provider.schema";
|
||||||
|
import { PayloadCmsProviderConfig } from "./payloadcms-provider.schema";
|
||||||
|
|
||||||
export namespace ProvidersConfig {
|
export namespace ProvidersConfig {
|
||||||
const AnyFull = z.union([
|
const AnyFull = z.union([
|
||||||
|
@ -14,6 +15,7 @@ export namespace ProvidersConfig {
|
||||||
DatocmsProviderConfig.Schema.Full,
|
DatocmsProviderConfig.Schema.Full,
|
||||||
StrapiProviderConfig.Schema.Full,
|
StrapiProviderConfig.Schema.Full,
|
||||||
BuilderIoProviderConfig.Schema.Full,
|
BuilderIoProviderConfig.Schema.Full,
|
||||||
|
PayloadCmsProviderConfig.Schema.Full,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const Schema = {
|
export const Schema = {
|
||||||
|
@ -23,6 +25,7 @@ export namespace ProvidersConfig {
|
||||||
DatocmsProviderConfig.Schema.Input,
|
DatocmsProviderConfig.Schema.Input,
|
||||||
StrapiProviderConfig.Schema.Input,
|
StrapiProviderConfig.Schema.Input,
|
||||||
BuilderIoProviderConfig.Schema.Input,
|
BuilderIoProviderConfig.Schema.Input,
|
||||||
|
PayloadCmsProviderConfig.Schema.Input,
|
||||||
]),
|
]),
|
||||||
AnyFullList: z.array(AnyFull),
|
AnyFullList: z.array(AnyFull),
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,8 @@ import { ProvidersConfig } from "../configuration";
|
||||||
|
|
||||||
import { ProvidersResolver } from "../providers/providers-resolver";
|
import { ProvidersResolver } from "../providers/providers-resolver";
|
||||||
import { trpcClient } from "../trpc/trpc-client";
|
import { trpcClient } from "../trpc/trpc-client";
|
||||||
import { ButtonsBox } from "../ui/buttons-box";
|
|
||||||
import { Skeleton } from "../ui/skeleton";
|
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
const ProvidersTable = (props: { providers: ProvidersConfig.AnyFullShape[] }) => {
|
const ProvidersTable = (props: { providers: ProvidersConfig.AnyFullShape[] }) => {
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
@ -47,30 +47,45 @@ export const ProvidersList = () => {
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Layout.AppSectionCard
|
||||||
|
footer={
|
||||||
|
<ButtonsBox>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
push("/add-provider");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add first CMS configuration
|
||||||
|
</Button>
|
||||||
|
</ButtonsBox>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Text as="p" marginBottom={4}>
|
<Text as="p" marginBottom={4}>
|
||||||
No configurations yet
|
No configurations yet
|
||||||
</Text>
|
</Text>
|
||||||
|
</Layout.AppSectionCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.AppSectionCard
|
||||||
|
footer={
|
||||||
<ButtonsBox>
|
<ButtonsBox>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
push("/add-provider");
|
push("/add-provider");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Add first CMS configuration
|
Add CMS configuration
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonsBox>
|
</ButtonsBox>
|
||||||
</Box>
|
}
|
||||||
);
|
>
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
{data.length && (
|
{data.length && (
|
||||||
<Box>
|
<Box>
|
||||||
<Text variant="heading" as="h2" marginBottom={4}>
|
<Text variant="heading" as="h2" marginBottom={4}>
|
||||||
|
@ -79,15 +94,6 @@ export const ProvidersList = () => {
|
||||||
<ProvidersTable providers={data} />
|
<ProvidersTable providers={data} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<ButtonsBox marginTop={8}>
|
</Layout.AppSectionCard>
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
push("/add-provider");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add CMS configuration
|
|
||||||
</Button>
|
|
||||||
</ButtonsBox>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,9 +7,7 @@ import { useForm } from "react-hook-form";
|
||||||
import { BuilderIoProviderConfig, SaleorProviderFieldsMappingKeys } from "../../configuration";
|
import { BuilderIoProviderConfig, SaleorProviderFieldsMappingKeys } from "../../configuration";
|
||||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { ButtonsBox } from "../../ui/buttons-box";
|
import { ButtonsBox, SkeletonLayout, TextLink } from "@saleor/apps-ui";
|
||||||
import { TextLink } from "@saleor/apps-ui";
|
|
||||||
import { Skeleton } from "@/modules/ui/skeleton";
|
|
||||||
|
|
||||||
type FormShape = Omit<BuilderIoProviderConfig.InputShape, "type">;
|
type FormShape = Omit<BuilderIoProviderConfig.InputShape, "type">;
|
||||||
const FormSchema = BuilderIoProviderConfig.Schema.Input.omit({ type: true });
|
const FormSchema = BuilderIoProviderConfig.Schema.Input.omit({ type: true });
|
||||||
|
@ -104,7 +102,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
padding={2}
|
padding={2}
|
||||||
>
|
>
|
||||||
<Text variant="caption">Saleor Field</Text>
|
<Text variant="caption">Saleor Field</Text>
|
||||||
<Text variant="caption">Contentful field</Text>
|
<Text variant="caption">Builder.io field</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
// todo extract this table to component
|
// todo extract this table to component
|
||||||
|
@ -127,7 +125,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
size="small"
|
size="small"
|
||||||
control={control}
|
control={control}
|
||||||
name={`productVariantFieldsMapping.${saleorField}`}
|
name={`productVariantFieldsMapping.${saleorField}`}
|
||||||
label="Builer.io Field"
|
label="Builder.io Field"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
@ -190,7 +188,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!props.configId,
|
enabled: !!props.configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
@ -207,11 +205,11 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type !== "builder.io") {
|
if (data.type !== "builder.io") {
|
||||||
throw new Error("Trying to fill builer.io form with non builder.io data");
|
throw new Error("Trying to fill builder.io form with non builder.io data");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -9,8 +9,7 @@ import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
import { ContentfulProviderConfig } from "../../configuration/schemas/contentful-provider.schema";
|
import { ContentfulProviderConfig } from "../../configuration/schemas/contentful-provider.schema";
|
||||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { ButtonsBox } from "../../ui/buttons-box";
|
import { ButtonsBox, TextLink } from "@saleor/apps-ui";
|
||||||
import { TextLink } from "@saleor/apps-ui";
|
|
||||||
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
|
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
|
||||||
|
|
||||||
type FormSchema = Omit<ContentfulProviderConfig.InputShape, "type">;
|
type FormSchema = Omit<ContentfulProviderConfig.InputShape, "type">;
|
||||||
|
@ -53,7 +52,7 @@ const PureForm = ({
|
||||||
});
|
});
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch content types from Contentful. Please check your credentials."
|
"Could not fetch content types from Contentful. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -76,7 +75,7 @@ const PureForm = ({
|
||||||
});
|
});
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch environments from Contentful. Please check your credentials."
|
"Could not fetch environments from Contentful. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -197,7 +196,7 @@ const PureForm = ({
|
||||||
newTab
|
newTab
|
||||||
size="small"
|
size="small"
|
||||||
href={`https://app.contentful.com/spaces/${getValues(
|
href={`https://app.contentful.com/spaces/${getValues(
|
||||||
"spaceId"
|
"spaceId",
|
||||||
)}/settings/environments`}
|
)}/settings/environments`}
|
||||||
>
|
>
|
||||||
here
|
here
|
||||||
|
@ -247,7 +246,7 @@ const PureForm = ({
|
||||||
|
|
||||||
<Box marginTop={4}>
|
<Box marginTop={4}>
|
||||||
<Text as="p" variant="heading" size="small">
|
<Text as="p" variant="heading" size="small">
|
||||||
Map fields from Saleor to your contentful schema.
|
Map fields from Saleor to your Contentful schema.
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="p" marginTop={2} marginBottom={4}>
|
<Text as="p" marginTop={2} marginBottom={4}>
|
||||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
||||||
|
@ -357,7 +356,7 @@ const EditVariant = ({ configId }: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!configId,
|
enabled: !!configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import React, { useEffect, useMemo } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { ButtonsBox } from "../../ui/buttons-box";
|
|
||||||
import { DatocmsProviderConfig } from "@/modules/configuration/schemas/datocms-provider.schema";
|
import { DatocmsProviderConfig } from "@/modules/configuration/schemas/datocms-provider.schema";
|
||||||
|
import { ButtonsBox } from "@saleor/apps-ui";
|
||||||
|
|
||||||
type FormShape = Omit<DatocmsProviderConfig.InputShape, "type">;
|
type FormShape = Omit<DatocmsProviderConfig.InputShape, "type">;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
|
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch content types from DatoCMS. Please check your credentials."
|
"Could not fetch content types from DatoCMS. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
|
|
||||||
notifyError(
|
notifyError(
|
||||||
"Error",
|
"Error",
|
||||||
"Could not fetch content types from DatoCMS. Please check your credentials."
|
"Could not fetch content types from DatoCMS. Please check your credentials.",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -161,7 +161,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
{fieldsData && (
|
{fieldsData && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Text as="p" variant="heading" size="small">
|
<Text as="p" variant="heading" size="small">
|
||||||
Map fields from Saleor to your contentful schema.
|
Map fields from Saleor to your DatoCMS schema.
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="p" marginTop={2} marginBottom={4}>
|
<Text as="p" marginTop={2} marginBottom={4}>
|
||||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
||||||
|
@ -177,7 +177,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
padding={2}
|
padding={2}
|
||||||
>
|
>
|
||||||
<Text variant="caption">Saleor Field</Text>
|
<Text variant="caption">Saleor Field</Text>
|
||||||
<Text variant="caption">Contentful field</Text>
|
<Text variant="caption">DatoCMS field</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
// todo extract this table to component
|
// todo extract this table to component
|
||||||
|
@ -271,7 +271,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!props.configId,
|
enabled: !!props.configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
|
BIN
apps/cms-v2/src/modules/providers/payloadcms/logo.png
Normal file
BIN
apps/cms-v2/src/modules/providers/payloadcms/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1,51 @@
|
||||||
|
import { BulkImportProductFragment } from "../../../../generated/graphql";
|
||||||
|
import { BulkSyncProcessor, BulkSyncProcessorHooks } from "../../bulk-sync/bulk-sync-processor";
|
||||||
|
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { PayloadCMSClient } from "./payloadcms-client";
|
||||||
|
|
||||||
|
// todo CORS or proxy
|
||||||
|
export class PayloadCmsBulkSyncProcessor implements BulkSyncProcessor {
|
||||||
|
constructor(private config: PayloadCmsProviderConfig.FullShape) {}
|
||||||
|
|
||||||
|
async uploadProducts(
|
||||||
|
products: BulkImportProductFragment[],
|
||||||
|
hooks: BulkSyncProcessorHooks,
|
||||||
|
): Promise<void> {
|
||||||
|
const client = new PayloadCMSClient();
|
||||||
|
|
||||||
|
products.flatMap(
|
||||||
|
(product) =>
|
||||||
|
product.variants?.map((variant) => {
|
||||||
|
if (hooks.onUploadStart) {
|
||||||
|
hooks.onUploadStart({ variantId: variant.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
.upsertProductVariant({
|
||||||
|
configuration: this.config,
|
||||||
|
variant: {
|
||||||
|
id: variant.id,
|
||||||
|
name: variant.name,
|
||||||
|
channelListings: variant.channelListings,
|
||||||
|
product: {
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
slug: product.slug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((r) => {
|
||||||
|
if (hooks.onUploadSuccess) {
|
||||||
|
hooks.onUploadSuccess({ variantId: variant.id });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (hooks.onUploadError) {
|
||||||
|
hooks.onUploadError({ variantId: variant.id, error: e });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
import { WebhookProductVariantFragment } from "../../../../generated/graphql";
|
||||||
|
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { FieldsMapper } from "../fields-mapper";
|
||||||
|
|
||||||
|
import qs from "qs";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
configuration: PayloadCmsProviderConfig.FullShape;
|
||||||
|
variant: WebhookProductVariantFragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client uses REST API with built-in query language
|
||||||
|
* https://payloadcms.com/docs/queries/overview#rest-queries
|
||||||
|
*/
|
||||||
|
export class PayloadCMSClient {
|
||||||
|
private logger = createLogger({ name: "PayloadCMSClient" });
|
||||||
|
|
||||||
|
private mapVariantToPayloadFields({ configuration, variant }: Context) {
|
||||||
|
const fields = FieldsMapper.mapProductVariantToConfigurationFields({
|
||||||
|
variant,
|
||||||
|
configMapping: configuration.productVariantFieldsMapping,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructCollectionUrl(config: PayloadCmsProviderConfig.FullShape) {
|
||||||
|
return `${config.payloadApiUrl}/${config.collectionName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemsBySaleorVariantId(context: Context) {
|
||||||
|
const queryString = qs.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
[context.configuration.productVariantFieldsMapping.variantId]: {
|
||||||
|
equals: context.variant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return fetch(`${this.constructCollectionUrl(context.configuration)}${queryString}`, {
|
||||||
|
headers: this.getHeaders(context),
|
||||||
|
}).then((r) => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteProductVariant(context: Context) {
|
||||||
|
const queryString = qs.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
[context.configuration.productVariantFieldsMapping.variantId]: {
|
||||||
|
equals: context.variant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
this.constructCollectionUrl(context.configuration) + queryString,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: this.getHeaders(context),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error("Error while deleting product variant");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeaders(context: Context) {
|
||||||
|
const headers = new Headers({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://payloadcms.com/docs/authentication/config#api-keys
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
context.configuration.authToken.length > 0 &&
|
||||||
|
context.configuration.authenticatedUserSlug.length > 0
|
||||||
|
) {
|
||||||
|
headers.append(
|
||||||
|
"Authorization",
|
||||||
|
`${context.configuration.authenticatedUserSlug} API-Key ${context.configuration.authToken}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadProductVariant(context: Context) {
|
||||||
|
this.logger.debug("Trying to upload product variant");
|
||||||
|
|
||||||
|
return fetch(this.constructCollectionUrl(context.configuration), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(this.mapVariantToPayloadFields(context)),
|
||||||
|
headers: this.getHeaders(context),
|
||||||
|
})
|
||||||
|
.then((r) => {
|
||||||
|
if (r.status >= 400) {
|
||||||
|
throw new Error(`Error while uploading product variant: ${r.statusText}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.logger.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProductVariant({ configuration, variant }: Context) {
|
||||||
|
this.logger.debug("Trying to update product variant");
|
||||||
|
|
||||||
|
const queryString = qs.stringify(
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
[configuration.productVariantFieldsMapping.variantId]: {
|
||||||
|
equals: variant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
addQueryPrefix: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.constructCollectionUrl(configuration) + queryString, {
|
||||||
|
method: "PATCH",
|
||||||
|
body: JSON.stringify(this.mapVariantToPayloadFields({ configuration, variant })),
|
||||||
|
headers: this.getHeaders({ configuration, variant }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status >= 400) {
|
||||||
|
throw new Error("Error while updating product variant");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(e);
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertProductVariant(context: Context) {
|
||||||
|
this.logger.debug("Trying to upsert product variant");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.uploadProductVariant(context);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.debug("Failed to upload, will try to update");
|
||||||
|
|
||||||
|
await this.updateProductVariant(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,264 @@
|
||||||
|
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
|
import { Input, Select } from "@saleor/react-hook-form-macaw";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||||
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
|
import { ButtonsBox, TextLink } from "@saleor/apps-ui";
|
||||||
|
|
||||||
|
type FormShape = Omit<PayloadCmsProviderConfig.InputShape, "type">;
|
||||||
|
|
||||||
|
type PureFormProps = {
|
||||||
|
defaultValues: FormShape;
|
||||||
|
onSubmit(values: FormShape): void;
|
||||||
|
onDelete?(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* todo react on token change, refresh mutation
|
||||||
|
*/
|
||||||
|
const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
|
const { notifyError } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { control, getValues, setValue, watch, handleSubmit, clearErrors, setError } = useForm({
|
||||||
|
defaultValues: defaultValues,
|
||||||
|
resolver: zodResolver(PayloadCmsProviderConfig.Schema.Input.omit({ type: true })),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="form"
|
||||||
|
display={"grid"}
|
||||||
|
gap={4}
|
||||||
|
onSubmit={handleSubmit((vals) => {
|
||||||
|
onSubmit(vals);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
control={control}
|
||||||
|
name="configName"
|
||||||
|
label="Configuration name"
|
||||||
|
helperText="Meaningful name that will help you understand it later. E.g. 'staging' or 'prod' "
|
||||||
|
/>
|
||||||
|
<Box display={"grid"} gap={4} marginY={4}>
|
||||||
|
<Text variant="heading">Provide connection details</Text>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
control={control}
|
||||||
|
name="payloadApiUrl"
|
||||||
|
type="url"
|
||||||
|
label="API url"
|
||||||
|
helperText="URL where Payload API is available. By default ends with /api"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
backgroundColor="surfaceNeutralHighlight"
|
||||||
|
borderColor="neutralHighlight"
|
||||||
|
borderWidth={1}
|
||||||
|
borderStyle={"solid"}
|
||||||
|
padding={4}
|
||||||
|
borderRadius={4}
|
||||||
|
>
|
||||||
|
<Text variant="heading" as="h1" marginBottom={4}>
|
||||||
|
Authorization
|
||||||
|
</Text>
|
||||||
|
<Text marginBottom={2} as="p">
|
||||||
|
Payload can be configured to have open operations (not recommended) or to require an API
|
||||||
|
key. Key can be generated per user. To authenticate, you need to provide both user slug
|
||||||
|
and the key itself.{" "}
|
||||||
|
</Text>
|
||||||
|
<TextLink
|
||||||
|
marginBottom={2}
|
||||||
|
display="block"
|
||||||
|
newTab
|
||||||
|
href="https://payloadcms.com/docs/authentication/config"
|
||||||
|
>
|
||||||
|
Read more in Payload docs
|
||||||
|
</TextLink>
|
||||||
|
<Text as="p" marginBottom={6}>
|
||||||
|
If your API is open (e.g. for development purposes) leave both fields empty.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Box display="grid" gap={4} gridTemplateColumns={2}>
|
||||||
|
<Input
|
||||||
|
control={control}
|
||||||
|
name="authenticatedUserSlug"
|
||||||
|
label="Authenticated user slug"
|
||||||
|
placeholder="e.g. apps"
|
||||||
|
/>
|
||||||
|
<Input control={control} name="authToken" type="password" label="User API Key" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box display={"grid"} gap={4} marginY={4}>
|
||||||
|
<Text variant="heading">Configure fields mapping</Text>
|
||||||
|
<Input
|
||||||
|
label="Collection Slug"
|
||||||
|
name="collectionName"
|
||||||
|
control={control}
|
||||||
|
helperText="Slug of your collection in Payload, e.g. 'saleorVariants'"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<React.Fragment>
|
||||||
|
<Text as="p" variant="heading" size="small">
|
||||||
|
Map fields from Saleor to your Payload schema.
|
||||||
|
</Text>
|
||||||
|
<Text as="p" marginTop={2} marginBottom={4}>
|
||||||
|
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should be
|
||||||
|
type of <Text variant="bodyStrong">JSON</Text>.
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
marginBottom={4}
|
||||||
|
display="grid"
|
||||||
|
__gridTemplateColumns={"50% 50%"}
|
||||||
|
borderBottomWidth={1}
|
||||||
|
borderBottomStyle="solid"
|
||||||
|
borderColor="neutralHighlight"
|
||||||
|
padding={2}
|
||||||
|
>
|
||||||
|
<Text variant="caption">Saleor Field</Text>
|
||||||
|
<Text variant="caption">Payload field</Text>
|
||||||
|
</Box>
|
||||||
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
|
// todo extract this table to component
|
||||||
|
<Box
|
||||||
|
display="grid"
|
||||||
|
__gridTemplateColumns={"50% 50%"}
|
||||||
|
padding={2}
|
||||||
|
key={saleorField}
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Text as="p" variant="bodyStrong">
|
||||||
|
{printSaleorProductFields(saleorField)}
|
||||||
|
</Text>
|
||||||
|
<Text variant="caption">
|
||||||
|
{saleorField === "channels" ? "JSON field" : "Text field"}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Input
|
||||||
|
size="small"
|
||||||
|
control={control}
|
||||||
|
name={`productVariantFieldsMapping.${saleorField}`}
|
||||||
|
label="CMS Field"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<ButtonsBox>
|
||||||
|
{onDelete && (
|
||||||
|
<Button onClick={onDelete} variant="tertiary">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button type="submit">Save</Button>
|
||||||
|
</ButtonsBox>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddFormVariant = () => {
|
||||||
|
const { push } = useRouter();
|
||||||
|
const { notifySuccess } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { mutate } = trpcClient.providersConfigs.addOne.useMutation({
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess("Success", "Added new configuration");
|
||||||
|
push("/configuration");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PureForm
|
||||||
|
onSubmit={(values) => {
|
||||||
|
mutate({
|
||||||
|
...values,
|
||||||
|
type: "payloadcms",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultValues={{
|
||||||
|
payloadApiUrl: "",
|
||||||
|
authToken: "",
|
||||||
|
configName: "",
|
||||||
|
collectionName: "",
|
||||||
|
authenticatedUserSlug: "",
|
||||||
|
productVariantFieldsMapping: {
|
||||||
|
channels: "",
|
||||||
|
variantName: "",
|
||||||
|
productId: "",
|
||||||
|
productName: "",
|
||||||
|
productSlug: "",
|
||||||
|
variantId: "",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditFormVariant = (props: { configId: string }) => {
|
||||||
|
const { push } = useRouter();
|
||||||
|
const { notifySuccess } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { data } = trpcClient.providersConfigs.getOne.useQuery(
|
||||||
|
{
|
||||||
|
id: props.configId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!props.configId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess("Success", "Updated configuration");
|
||||||
|
push("/configuration");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: deleteProvider } = trpcClient.providersConfigs.deleteOne.useMutation({
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess("Success", "Removed configuration");
|
||||||
|
push("/configuration");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type !== "payloadcms") {
|
||||||
|
throw new Error("Trying to fill Payload CMS form with non Payload CMS data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PureForm
|
||||||
|
onDelete={() => {
|
||||||
|
deleteProvider({
|
||||||
|
id: props.configId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
mutate({
|
||||||
|
...values,
|
||||||
|
type: "payloadcms",
|
||||||
|
id: props.configId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
defaultValues={data}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PayloadCMSConfigForm = {
|
||||||
|
PureVariant: PureForm,
|
||||||
|
AddVariant: AddFormVariant,
|
||||||
|
EditVariant: EditFormVariant,
|
||||||
|
};
|
|
@ -0,0 +1,69 @@
|
||||||
|
import {
|
||||||
|
WebhookProductFragment,
|
||||||
|
WebhookProductVariantFragment,
|
||||||
|
} from "../../../../generated/graphql";
|
||||||
|
|
||||||
|
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
import { ProductWebhooksProcessor } from "../../webhooks-operations/product-webhooks-processor";
|
||||||
|
import { PayloadCMSClient } from "./payloadcms-client";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* todo error handling
|
||||||
|
*/
|
||||||
|
export class PayloadCmsWebhooksProcessor implements ProductWebhooksProcessor {
|
||||||
|
private client = new PayloadCMSClient();
|
||||||
|
|
||||||
|
private logger = createLogger({ name: "PayloadCmsWebhooksProcessor" });
|
||||||
|
|
||||||
|
constructor(private providerConfig: PayloadCmsProviderConfig.FullShape) {}
|
||||||
|
|
||||||
|
async onProductVariantUpdated(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductVariantUpdated called");
|
||||||
|
|
||||||
|
await this.client.upsertProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: productVariant,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onProductVariantCreated(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductVariantCreated called");
|
||||||
|
|
||||||
|
await this.client.uploadProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: productVariant,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async onProductVariantDeleted(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductVariantDeleted called");
|
||||||
|
|
||||||
|
await this.client.deleteProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: productVariant,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onProductUpdated(product: WebhookProductFragment): Promise<void> {
|
||||||
|
this.logger.trace("onProductUpdated called");
|
||||||
|
|
||||||
|
const client = new PayloadCMSClient();
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
(product.variants ?? []).map((variant) => {
|
||||||
|
return client.upsertProductVariant({
|
||||||
|
configuration: this.providerConfig,
|
||||||
|
variant: {
|
||||||
|
id: variant.id,
|
||||||
|
name: variant.name,
|
||||||
|
product: {
|
||||||
|
id: product.id,
|
||||||
|
name: product.name,
|
||||||
|
slug: product.slug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
29
apps/cms-v2/src/modules/providers/payloadcms/payloadcms.tsx
Normal file
29
apps/cms-v2/src/modules/providers/payloadcms/payloadcms.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
|
import { CMSProviderMeta } from "../cms-provider-meta";
|
||||||
|
import logo from "./logo.png";
|
||||||
|
import { TextLink } from "@saleor/apps-ui";
|
||||||
|
|
||||||
|
export const PayloadCMS = {
|
||||||
|
formSideInfo: (
|
||||||
|
<Box>
|
||||||
|
<Text as="p" marginBottom={2}>
|
||||||
|
Configure the Payload CMS integration by providing required information.
|
||||||
|
</Text>
|
||||||
|
<Text as="p" marginBottom={2}>
|
||||||
|
Fields are not validated - ensure you enter correct values.
|
||||||
|
</Text>
|
||||||
|
<Text as="p" marginBottom={2}>
|
||||||
|
Consult{" "}
|
||||||
|
<TextLink newTab href="https://docs.saleor.io/docs/3.x/developer/app-store/apps/cms">
|
||||||
|
docs
|
||||||
|
</TextLink>{" "}
|
||||||
|
for more information how to set up Payload CMS.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
type: "payloadcms" as const,
|
||||||
|
logoUrl: logo.src,
|
||||||
|
displayName: "Payload",
|
||||||
|
description: "Open source, typescript first headless CMS. GraphQL included.",
|
||||||
|
} satisfies CMSProviderMeta;
|
|
@ -1,12 +1,24 @@
|
||||||
import { BuilderIo } from "./builder.io/builder-io";
|
import { BuilderIo } from "./builder.io/builder-io";
|
||||||
import { Contentful } from "./contentful/contentful";
|
import { Contentful } from "./contentful/contentful";
|
||||||
import { Datocms } from "./datocms/datocms";
|
import { Datocms } from "./datocms/datocms";
|
||||||
|
import { PayloadCMS } from "./payloadcms/payloadcms";
|
||||||
import { Strapi } from "./strapi/strapi";
|
import { Strapi } from "./strapi/strapi";
|
||||||
|
|
||||||
export type CMS = typeof Contentful | typeof Datocms | typeof Strapi | typeof BuilderIo;
|
export type CMS =
|
||||||
|
| typeof Contentful
|
||||||
|
| typeof Datocms
|
||||||
|
| typeof Strapi
|
||||||
|
| typeof BuilderIo
|
||||||
|
| typeof PayloadCMS;
|
||||||
|
|
||||||
export type CMSType = CMS["type"];
|
export type CMSType = CMS["type"];
|
||||||
|
|
||||||
export const cmsTypes = [Contentful.type, Datocms.type, Strapi.type, BuilderIo.type] as const;
|
export const cmsTypes = [
|
||||||
|
Contentful.type,
|
||||||
|
Datocms.type,
|
||||||
|
Strapi.type,
|
||||||
|
BuilderIo.type,
|
||||||
|
PayloadCMS.type,
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const CMSProviders = [Contentful, Datocms, Strapi, BuilderIo] as const;
|
export const CMSProviders = [Contentful, Datocms, Strapi, BuilderIo, PayloadCMS] as const;
|
||||||
|
|
|
@ -22,6 +22,10 @@ import { DatocmsProviderConfig } from "../configuration/schemas/datocms-provider
|
||||||
import { BuilderIo } from "./builder.io/builder-io";
|
import { BuilderIo } from "./builder.io/builder-io";
|
||||||
import { BuilderIoWebhooksProcessor } from "./builder.io/builder-io-webhooks-processor";
|
import { BuilderIoWebhooksProcessor } from "./builder.io/builder-io-webhooks-processor";
|
||||||
import { BuilderIoBulkSyncProcessor } from "./builder.io/builder-io-bulk-sync-processor";
|
import { BuilderIoBulkSyncProcessor } from "./builder.io/builder-io-bulk-sync-processor";
|
||||||
|
import { PayloadCmsBulkSyncProcessor } from "./payloadcms/payloadcms-bulk-sync-processor";
|
||||||
|
import { PayloadCmsProviderConfig } from "../configuration/schemas/payloadcms-provider.schema";
|
||||||
|
import { PayloadCMS } from "./payloadcms/payloadcms";
|
||||||
|
import { PayloadCmsWebhooksProcessor } from "./payloadcms/payloadcms-webhooks-processor";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Almost-single source of new providers. Every time app will need to resolve a provider, it will use on of these factories.
|
* Almost-single source of new providers. Every time app will need to resolve a provider, it will use on of these factories.
|
||||||
|
@ -39,9 +43,9 @@ export const ProvidersResolver = {
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return new BuilderIoBulkSyncProcessor(config);
|
return new BuilderIoBulkSyncProcessor(config);
|
||||||
}
|
}
|
||||||
|
case "payloadcms": {
|
||||||
default:
|
return new PayloadCmsBulkSyncProcessor(config);
|
||||||
throw new Error(`Unknown provider`);
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProviderInputSchema(type: CMSType) {
|
getProviderInputSchema(type: CMSType) {
|
||||||
|
@ -54,9 +58,8 @@ export const ProvidersResolver = {
|
||||||
return StrapiProviderConfig.Schema.Input;
|
return StrapiProviderConfig.Schema.Input;
|
||||||
case "builder.io":
|
case "builder.io":
|
||||||
return BuilderIoProviderConfig.Schema.Input;
|
return BuilderIoProviderConfig.Schema.Input;
|
||||||
default: {
|
case "payloadcms":
|
||||||
throw new Error("Failed to build input schema");
|
return PayloadCmsProviderConfig.Schema.Input;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getProviderSchema(type: CMSType) {
|
getProviderSchema(type: CMSType) {
|
||||||
|
@ -69,12 +72,11 @@ export const ProvidersResolver = {
|
||||||
return StrapiProviderConfig.Schema.Full;
|
return StrapiProviderConfig.Schema.Full;
|
||||||
case "builder.io":
|
case "builder.io":
|
||||||
return BuilderIoProviderConfig.Schema.Full;
|
return BuilderIoProviderConfig.Schema.Full;
|
||||||
default: {
|
case "payloadcms":
|
||||||
throw new Error("Failed to build provdier schema");
|
return PayloadCmsProviderConfig.Schema.Full;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createProviderMeta(type: CMSType | string): CMS {
|
createProviderMeta(type: CMSType): CMS {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "contentful": {
|
case "contentful": {
|
||||||
return Contentful;
|
return Contentful;
|
||||||
|
@ -88,8 +90,8 @@ export const ProvidersResolver = {
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return BuilderIo;
|
return BuilderIo;
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Unknown provider");
|
return PayloadCMS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -107,13 +109,13 @@ export const ProvidersResolver = {
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return new BuilderIoWebhooksProcessor(config);
|
return new BuilderIoWebhooksProcessor(config);
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Failed to build webhook processor.");
|
return new PayloadCmsWebhooksProcessor(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getEditProviderFormComponent: (
|
getEditProviderFormComponent: (
|
||||||
type: CMSType
|
type: CMSType,
|
||||||
): ComponentType<{
|
): ComponentType<{
|
||||||
configId: string;
|
configId: string;
|
||||||
}> => {
|
}> => {
|
||||||
|
@ -121,33 +123,37 @@ export const ProvidersResolver = {
|
||||||
case "contentful": {
|
case "contentful": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./contentful/contentful-config-form").then(
|
import("./contentful/contentful-config-form").then(
|
||||||
(module) => module.ContentfulConfigForm.EditVariant
|
(module) => module.ContentfulConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "datocms": {
|
case "datocms": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./datocms/datocms-config-form").then(
|
import("./datocms/datocms-config-form").then(
|
||||||
(module) => module.DatoCMSConfigForm.EditVariant
|
(module) => module.DatoCMSConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "strapi": {
|
case "strapi": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./strapi/strapi-config-form").then(
|
import("./strapi/strapi-config-form").then(
|
||||||
(module) => module.StrapiConfigForm.EditVariant
|
(module) => module.StrapiConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./builder.io/builder-io-config-form").then(
|
import("./builder.io/builder-io-config-form").then(
|
||||||
(module) => module.BuilderIoConfigForm.EditVariant
|
(module) => module.BuilderIoConfigForm.EditVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Provider form not registered");
|
return dynamic(() =>
|
||||||
|
import("./payloadcms/payloadcms-config-form").then(
|
||||||
|
(module) => module.PayloadCMSConfigForm.EditVariant,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -156,31 +162,37 @@ export const ProvidersResolver = {
|
||||||
case "contentful": {
|
case "contentful": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./contentful/contentful-config-form").then(
|
import("./contentful/contentful-config-form").then(
|
||||||
(module) => module.ContentfulConfigForm.AddVariant
|
(module) => module.ContentfulConfigForm.AddVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "datocms": {
|
case "datocms": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./datocms/datocms-config-form").then(
|
import("./datocms/datocms-config-form").then(
|
||||||
(module) => module.DatoCMSConfigForm.AddVariant
|
(module) => module.DatoCMSConfigForm.AddVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "strapi": {
|
case "strapi": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./strapi/strapi-config-form").then((module) => module.StrapiConfigForm.AddVariant)
|
import("./strapi/strapi-config-form").then(
|
||||||
|
(module) => module.StrapiConfigForm.AddVariant,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "builder.io": {
|
case "builder.io": {
|
||||||
return dynamic(() =>
|
return dynamic(() =>
|
||||||
import("./builder.io/builder-io-config-form").then(
|
import("./builder.io/builder-io-config-form").then(
|
||||||
(module) => module.BuilderIoConfigForm.AddVariant
|
(module) => module.BuilderIoConfigForm.AddVariant,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
case "payloadcms": {
|
||||||
throw new Error("Provider form not registered");
|
return dynamic(() =>
|
||||||
|
import("./payloadcms/payloadcms-config-form").then(
|
||||||
|
(module) => module.PayloadCMSConfigForm.AddVariant,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,8 +7,7 @@ import { useForm } from "react-hook-form";
|
||||||
import { SaleorProviderFieldsMappingKeys, StrapiProviderConfig } from "../../configuration";
|
import { SaleorProviderFieldsMappingKeys, StrapiProviderConfig } from "../../configuration";
|
||||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { ButtonsBox } from "../../ui/buttons-box";
|
import { ButtonsBox, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
import { Skeleton } from "@/modules/ui/skeleton";
|
|
||||||
|
|
||||||
type FormShape = Omit<StrapiProviderConfig.InputShape, "type">;
|
type FormShape = Omit<StrapiProviderConfig.InputShape, "type">;
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||||
padding={2}
|
padding={2}
|
||||||
>
|
>
|
||||||
<Text variant="caption">Saleor Field</Text>
|
<Text variant="caption">Saleor Field</Text>
|
||||||
<Text variant="caption">Contentful field</Text>
|
<Text variant="caption">Strapi field</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||||
// todo extract this table to component
|
// todo extract this table to component
|
||||||
|
@ -172,7 +171,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!props.configId,
|
enabled: !!props.configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
@ -189,7 +188,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type !== "strapi") {
|
if (data.type !== "strapi") {
|
||||||
|
|
|
@ -9,7 +9,7 @@ dialog {
|
||||||
|
|
||||||
.dialog-overlay {
|
.dialog-overlay {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: color-mix(in srgb, var(--mu-colors-background-plain) 80%, transparent);
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
import * as trpcNext from "@trpc/server/adapters/next";
|
||||||
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
|
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
|
||||||
import { inferAsyncReturnType } from "@trpc/server";
|
import { inferAsyncReturnType } from "@trpc/server";
|
||||||
import { getBaseUrl } from "@/modules/shared/get-base-url";
|
import { getAppBaseUrl } from "@saleor/apps-shared";
|
||||||
|
|
||||||
export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
|
export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
|
||||||
const baseUrl = getBaseUrl(req.headers);
|
const baseUrl = getAppBaseUrl(req.headers);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,
|
token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
// todo move to shared
|
|
||||||
export const AppSection = ({
|
|
||||||
heading,
|
|
||||||
sideContent,
|
|
||||||
mainContent,
|
|
||||||
includePadding = true,
|
|
||||||
...props
|
|
||||||
}: PropsWithBox<{
|
|
||||||
heading: string;
|
|
||||||
sideContent?: ReactNode;
|
|
||||||
mainContent: ReactNode;
|
|
||||||
includePadding?: boolean;
|
|
||||||
}>) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
as="section"
|
|
||||||
__gridTemplateColumns={"400px auto"}
|
|
||||||
display={"grid"}
|
|
||||||
gap={10}
|
|
||||||
__maxWidth={"1200px"}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Text as="h2" variant={"heading"} size={"large"} marginBottom={1.5}>
|
|
||||||
{heading}
|
|
||||||
</Text>
|
|
||||||
{sideContent}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
borderStyle={"solid"}
|
|
||||||
borderColor={"neutralPlain"}
|
|
||||||
borderWidth={1}
|
|
||||||
padding={includePadding ? 5 : 0}
|
|
||||||
borderRadius={4}
|
|
||||||
>
|
|
||||||
{mainContent}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
||||||
import { forwardRef } from "react";
|
|
||||||
|
|
||||||
export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
||||||
return (
|
return (
|
||||||
|
@ -12,6 +11,8 @@ export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
||||||
as="dialog"
|
as="dialog"
|
||||||
__maxWidth="400px"
|
__maxWidth="400px"
|
||||||
boxShadow={"modal"}
|
boxShadow={"modal"}
|
||||||
|
backgroundColor="surfaceNeutralPlain"
|
||||||
|
color="textNeutralDefault"
|
||||||
open
|
open
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
|
||||||
|
|
||||||
// TODO: Make it more generic, move to shared or contribute to macaw
|
|
||||||
const Section = (props: BoxProps) => {
|
|
||||||
return (
|
|
||||||
<Box display="grid" gap={2} {...props}>
|
|
||||||
<Box
|
|
||||||
__height="10px"
|
|
||||||
backgroundColor="surfaceNeutralHighlight"
|
|
||||||
borderRadius={2}
|
|
||||||
__width="50%"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
__height="10px"
|
|
||||||
backgroundColor="surfaceNeutralHighlight"
|
|
||||||
borderRadius={2}
|
|
||||||
__width="70%"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
__height="10px"
|
|
||||||
backgroundColor="surfaceNeutralHighlight"
|
|
||||||
borderRadius={2}
|
|
||||||
__width="60%"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Skeleton = { Section };
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { CMSProviders } from "@/modules/providers/providers-registry";
|
import { CMSProviders } from "@/modules/providers/providers-registry";
|
||||||
import { AppHeader } from "@/modules/ui/app-header";
|
import { AppHeader } from "@/modules/ui/app-header";
|
||||||
import { AppSection } from "@/modules/ui/app-section";
|
import { Breadcrumbs, Layout } from "@saleor/apps-ui";
|
||||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
|
||||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -16,14 +15,15 @@ const AddProviderPage: NextPage = () => {
|
||||||
text="Connect CMS platforms to the App."
|
text="Connect CMS platforms to the App."
|
||||||
breadcrumbs={[<Breadcrumbs.Item key="provider">Add Provider</Breadcrumbs.Item>]}
|
breadcrumbs={[<Breadcrumbs.Item key="provider">Add Provider</Breadcrumbs.Item>]}
|
||||||
/>
|
/>
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
heading="Select CMS provider"
|
heading="Select CMS provider"
|
||||||
sideContent={
|
sideContent={
|
||||||
<Box>
|
<Box>
|
||||||
<Text>App allows to connect one or more CMS platforms. You can add more later.</Text>
|
<Text>App allows to connect one or more CMS platforms. You can add more later.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
mainContent={
|
>
|
||||||
|
<Layout.AppSectionCard>
|
||||||
<Box
|
<Box
|
||||||
display="grid"
|
display="grid"
|
||||||
__gridTemplateColumns="auto auto auto"
|
__gridTemplateColumns="auto auto auto"
|
||||||
|
@ -54,8 +54,8 @@ const AddProviderPage: NextPage = () => {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
}
|
</Layout.AppSectionCard>
|
||||||
/>
|
</Layout.AppSection>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import { ContentfulConfigForm } from "@/modules/providers/contentful/contentful-config-form";
|
|
||||||
import { DatoCMSConfigForm } from "@/modules/providers/datocms/datocms-config-form";
|
|
||||||
import { CMSType } from "@/modules/providers/providers-registry";
|
import { CMSType } from "@/modules/providers/providers-registry";
|
||||||
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
||||||
|
|
||||||
import { StrapiConfigForm } from "@/modules/providers/strapi/strapi-config-form";
|
|
||||||
import { AppHeader } from "@/modules/ui/app-header";
|
import { AppHeader } from "@/modules/ui/app-header";
|
||||||
import { AppSection } from "@/modules/ui/app-section";
|
import { Breadcrumbs, Layout } from "@saleor/apps-ui";
|
||||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
|
||||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -16,7 +12,7 @@ const AddProviderPage: NextPage = () => {
|
||||||
const { query } = useRouter();
|
const { query } = useRouter();
|
||||||
|
|
||||||
const provider = useMemo(() => {
|
const provider = useMemo(() => {
|
||||||
return query.type ? ProvidersResolver.createProviderMeta(query.type as string) : null;
|
return query.type ? ProvidersResolver.createProviderMeta(query.type as CMSType) : null;
|
||||||
}, [query.type]);
|
}, [query.type]);
|
||||||
|
|
||||||
if (!provider) return null;
|
if (!provider) return null;
|
||||||
|
@ -35,7 +31,7 @@ const AddProviderPage: NextPage = () => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
heading={`Set up ${provider.displayName}`}
|
heading={`Set up ${provider.displayName}`}
|
||||||
sideContent={
|
sideContent={
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -43,8 +39,11 @@ const AddProviderPage: NextPage = () => {
|
||||||
{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}
|
{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
mainContent={<FormComponent />}
|
>
|
||||||
/>
|
<Layout.AppSectionCard>
|
||||||
|
<FormComponent />
|
||||||
|
</Layout.AppSectionCard>
|
||||||
|
</Layout.AppSection>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default createManifestHandler({
|
||||||
],
|
],
|
||||||
homepageUrl: "https://github.com/saleor/apps",
|
homepageUrl: "https://github.com/saleor/apps",
|
||||||
id: "saleor.app.cms2",
|
id: "saleor.app.cms2",
|
||||||
name: "CMS 2",
|
name: "CMS",
|
||||||
permissions: ["MANAGE_PRODUCTS"],
|
permissions: ["MANAGE_PRODUCTS"],
|
||||||
requiredSaleorVersion: ">=3.10 <4",
|
requiredSaleorVersion: ">=3.10 <4",
|
||||||
supportUrl: "https://github.com/saleor/apps/discussions",
|
supportUrl: "https://github.com/saleor/apps/discussions",
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
import * as trpcNext from "@trpc/server/adapters/next";
|
||||||
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
||||||
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
|
||||||
|
const logger = createLogger({ name: "tRPC error" });
|
||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
export default trpcNext.createNextApiHandler({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: createTrpcContext,
|
createContext: createTrpcContext,
|
||||||
|
onError: ({ path, error }) => {
|
||||||
|
if (error.code === "INTERNAL_SERVER_ERROR") {
|
||||||
|
logger.error(error, `${path} returned error:`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(error, `${path} returned error:`);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { BulkSyncView } from "@/modules/bulk-sync/bulk-sync-view";
|
import { BulkSyncView } from "@/modules/bulk-sync/bulk-sync-view";
|
||||||
import { trpcClient } from "@/modules/trpc/trpc-client";
|
import { trpcClient } from "@/modules/trpc/trpc-client";
|
||||||
|
import { SkeletonLayout } from "@saleor/apps-ui";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Text } from "@saleor/macaw-ui/next";
|
|
||||||
import { Skeleton } from "@/modules/ui/skeleton";
|
|
||||||
|
|
||||||
const BulkSyncPage: NextPage = () => {
|
const BulkSyncPage: NextPage = () => {
|
||||||
const { query } = useRouter();
|
const { query } = useRouter();
|
||||||
|
@ -23,7 +22,7 @@ const BulkSyncPage: NextPage = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!parsedID,
|
enabled: !!parsedID,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -36,7 +35,7 @@ const BulkSyncPage: NextPage = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!connection,
|
enabled: !!connection,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((providerFetched && !provider) || (connectionFetched && !connection)) {
|
if ((providerFetched && !provider) || (connectionFetched && !connection)) {
|
||||||
|
@ -45,7 +44,7 @@ const BulkSyncPage: NextPage = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionLoading || providerLoading) {
|
if (connectionLoading || providerLoading) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(provider && connection)) {
|
if (!(provider && connection)) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { BulkSyncSection } from "@/modules/bulk-sync/bulk-sync-section";
|
||||||
import { ChannelProviderConnectionList } from "@/modules/channel-provider-connection/channels-provider-connection-list";
|
import { ChannelProviderConnectionList } from "@/modules/channel-provider-connection/channels-provider-connection-list";
|
||||||
import { ProvidersList } from "@/modules/providers-listing/providers-list";
|
import { ProvidersList } from "@/modules/providers-listing/providers-list";
|
||||||
import { AppHeader } from "@/modules/ui/app-header";
|
import { AppHeader } from "@/modules/ui/app-header";
|
||||||
import { AppSection } from "@/modules/ui/app-section";
|
import { Layout } from "@saleor/apps-ui";
|
||||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const ConfigurationPage: NextPage = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
marginBottom={14}
|
marginBottom={14}
|
||||||
heading="Providers configuration"
|
heading="Providers configuration"
|
||||||
sideContent={
|
sideContent={
|
||||||
|
@ -18,9 +18,10 @@ const ConfigurationPage: NextPage = () => {
|
||||||
<Text>Configure one or more CMS providers to synchronize Saleor products.</Text>
|
<Text>Configure one or more CMS providers to synchronize Saleor products.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
mainContent={<ProvidersList />}
|
>
|
||||||
/>
|
<ProvidersList />
|
||||||
<AppSection
|
</Layout.AppSection>
|
||||||
|
<Layout.AppSection
|
||||||
marginBottom={14}
|
marginBottom={14}
|
||||||
heading="Automatic synchronization"
|
heading="Automatic synchronization"
|
||||||
sideContent={
|
sideContent={
|
||||||
|
@ -31,9 +32,10 @@ const ConfigurationPage: NextPage = () => {
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
mainContent={<ChannelProviderConnectionList />}
|
>
|
||||||
/>
|
<ChannelProviderConnectionList />
|
||||||
<AppSection
|
</Layout.AppSection>
|
||||||
|
<Layout.AppSection
|
||||||
heading="Initial sync"
|
heading="Initial sync"
|
||||||
sideContent={
|
sideContent={
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -44,8 +46,9 @@ const ConfigurationPage: NextPage = () => {
|
||||||
<Text as="p">Its recommended to run this flow initially, once app is configured.</Text>
|
<Text as="p">Its recommended to run this flow initially, once app is configured.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
mainContent={<BulkSyncSection />}
|
>
|
||||||
/>
|
<BulkSyncSection />
|
||||||
|
</Layout.AppSection>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,9 +2,7 @@ import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
||||||
|
|
||||||
import { trpcClient } from "@/modules/trpc/trpc-client";
|
import { trpcClient } from "@/modules/trpc/trpc-client";
|
||||||
import { AppHeader } from "@/modules/ui/app-header";
|
import { AppHeader } from "@/modules/ui/app-header";
|
||||||
import { AppSection } from "@/modules/ui/app-section";
|
import { Breadcrumbs, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
import { Skeleton } from "@/modules/ui/skeleton";
|
|
||||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
|
||||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -20,7 +18,7 @@ const EditProviderPage: NextPage = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!configId,
|
enabled: !!configId,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const provider = useMemo(() => {
|
const provider = useMemo(() => {
|
||||||
|
@ -28,7 +26,7 @@ const EditProviderPage: NextPage = () => {
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFetched && !data) {
|
if (isFetched && !data) {
|
||||||
|
@ -38,7 +36,7 @@ const EditProviderPage: NextPage = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return <Skeleton.Section />;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditForm = ProvidersResolver.getEditProviderFormComponent(provider.type);
|
const EditForm = ProvidersResolver.getEditProviderFormComponent(provider.type);
|
||||||
|
@ -53,13 +51,16 @@ const EditProviderPage: NextPage = () => {
|
||||||
<Breadcrumbs.Item key="configname">{data?.configName}</Breadcrumbs.Item>,
|
<Breadcrumbs.Item key="configname">{data?.configName}</Breadcrumbs.Item>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
heading="Edit CMS configuration"
|
heading="Edit CMS configuration"
|
||||||
mainContent={<EditForm configId={configId} />}
|
|
||||||
sideContent={
|
sideContent={
|
||||||
<Box>{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}</Box>
|
<Box>{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}</Box>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<Layout.AppSectionCard>
|
||||||
|
<EditForm configId={configId} />
|
||||||
|
</Layout.AppSectionCard>
|
||||||
|
</Layout.AppSection>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
export {};
|
import { vi } from "vitest";
|
||||||
|
|
||||||
|
vi.stubEnv("SECRET_KEY", "TEST");
|
||||||
|
|
|
@ -1,5 +1,57 @@
|
||||||
# saleor-app-crm
|
# saleor-app-crm
|
||||||
|
|
||||||
|
## 1.7.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5dee65a: Updated dependencies:
|
||||||
|
- @graphql-codegen/cli@5.0.0
|
||||||
|
- 2e29699: Updated Sentry package
|
||||||
|
|
||||||
|
## 1.7.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- @saleor/apps-shared@1.9.0
|
||||||
|
|
||||||
|
## 1.7.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a81f061: Updated Macaw to pre-127
|
||||||
|
- fcc37e7: Remove clsx package from the projects no longer using it.
|
||||||
|
- Updated dependencies [2a1385b]
|
||||||
|
- Updated dependencies [a81f061]
|
||||||
|
- Updated dependencies [fcc37e7]
|
||||||
|
- @saleor/apps-shared@1.8.1
|
||||||
|
|
||||||
|
## 1.7.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8b3d961: Updated Macaw UI to pre.118
|
||||||
|
- Updated dependencies [8b3d961]
|
||||||
|
- Updated dependencies [c50797e]
|
||||||
|
- @saleor/apps-shared@1.8.0
|
||||||
|
|
||||||
|
## 1.7.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3002354: Added error logging for exceptions thrown at tRPC routes.
|
||||||
|
|
||||||
|
## 1.7.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 261957f: Updated dependencies: vite, vitest, eslint, prettier, dotenv, turbo, syncpack, changesets, lint staged
|
||||||
|
- Updated dependencies [261957f]
|
||||||
|
- @saleor/apps-shared@1.7.6
|
||||||
|
|
||||||
## 1.7.2
|
## 1.7.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "saleor-app-crm",
|
"name": "saleor-app-crm",
|
||||||
"version": "1.7.2",
|
"version": "1.7.8",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm generate && next build",
|
"build": "pnpm generate && next build",
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||||
|
@ -13,17 +13,17 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mailchimp/mailchimp_marketing": "^3.0.80",
|
"@mailchimp/mailchimp_marketing": "^3.0.80",
|
||||||
"@saleor/app-sdk": "0.41.1",
|
"@saleor/app-sdk": "0.43.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.67.0",
|
||||||
"@tanstack/react-query": "4.29.19",
|
"@tanstack/react-query": "4.29.19",
|
||||||
"@trpc/client": "10.34.0",
|
"@trpc/client": "10.38.1",
|
||||||
"@trpc/next": "10.34.0",
|
"@trpc/next": "10.38.1",
|
||||||
"@trpc/react-query": "10.34.0",
|
"@trpc/react-query": "10.38.1",
|
||||||
"@trpc/server": "10.34.0",
|
"@trpc/server": "10.38.1",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.4",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"graphql": "16.7.1",
|
"graphql": "16.7.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
@ -37,24 +37,24 @@
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.4.8",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.34.1",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "4.0.1",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
"@graphql-codegen/typescript": "4.0.1",
|
"@graphql-codegen/typescript": "4.0.1",
|
||||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@types/mailchimp__mailchimp_marketing": "^3.0.7",
|
"@types/mailchimp__mailchimp_marketing": "^3.0.7",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"node-mocks-http": "^1.12.2",
|
"node-mocks-http": "^1.12.2",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
import * as trpcNext from "@trpc/server/adapters/next";
|
||||||
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
||||||
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
|
||||||
|
const logger = createLogger({ name: "tRPC error" });
|
||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
export default trpcNext.createNextApiHandler({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: createTrpcContext,
|
createContext: createTrpcContext,
|
||||||
|
onError: ({ path, error }) => {
|
||||||
|
if (error.code === "INTERNAL_SERVER_ERROR") {
|
||||||
|
logger.error(error, `${path} returned error:`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(error, `${path} returned error:`);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,51 @@
|
||||||
# saleor-app-data-importer
|
# saleor-app-data-importer
|
||||||
|
|
||||||
|
## 1.9.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5dee65a: Updated dependencies:
|
||||||
|
- @graphql-codegen/cli@5.0.0
|
||||||
|
- 2e29699: Updated Sentry package
|
||||||
|
|
||||||
|
## 1.9.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- @saleor/apps-shared@1.9.0
|
||||||
|
|
||||||
|
## 1.9.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a81f061: Updated Macaw to pre-127
|
||||||
|
- fcc37e7: Remove clsx package from the projects no longer using it.
|
||||||
|
- Updated dependencies [2a1385b]
|
||||||
|
- Updated dependencies [a81f061]
|
||||||
|
- Updated dependencies [fcc37e7]
|
||||||
|
- @saleor/apps-shared@1.8.1
|
||||||
|
|
||||||
|
## 1.9.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8b3d961: Updated Macaw UI to pre.118
|
||||||
|
- Updated dependencies [8b3d961]
|
||||||
|
- Updated dependencies [c50797e]
|
||||||
|
- @saleor/apps-shared@1.8.0
|
||||||
|
|
||||||
|
## 1.9.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 261957f: Updated dependencies: vite, vitest, eslint, prettier, dotenv, turbo, syncpack, changesets, lint staged
|
||||||
|
- Updated dependencies [261957f]
|
||||||
|
- @saleor/apps-shared@1.7.6
|
||||||
|
|
||||||
## 1.9.2
|
## 1.9.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "saleor-app-data-importer",
|
"name": "saleor-app-data-importer",
|
||||||
"version": "1.9.2",
|
"version": "1.9.7",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm generate && next build",
|
"build": "pnpm generate && next build",
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||||
|
@ -15,13 +15,12 @@
|
||||||
"@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.41.1",
|
"@saleor/app-sdk": "0.43.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.67.0",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.4",
|
||||||
"clsx": "^1.2.1",
|
|
||||||
"dot-object": "^2.1.4",
|
"dot-object": "^2.1.4",
|
||||||
"graphql": "16.7.1",
|
"graphql": "16.7.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
@ -35,12 +34,12 @@
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.4.8",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.34.1",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "4.0.1",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/schema-ast": "4.0.0",
|
"@graphql-codegen/schema-ast": "4.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
|
@ -48,13 +47,12 @@
|
||||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@types/dot-object": "^2.1.2",
|
"@types/dot-object": "^2.1.2",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"@vitest/coverage-c8": "^0.28.4",
|
"eslint": "8.46.0",
|
||||||
"eslint": "8.44.0",
|
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,12 @@ APL=
|
||||||
REST_APL_ENDPOINT=
|
REST_APL_ENDPOINT=
|
||||||
REST_APL_TOKEN=
|
REST_APL_TOKEN=
|
||||||
|
|
||||||
|
# To use Redis as an APL store, set APP_API_BASE_URL and REDIS_URL.
|
||||||
|
# URL is in format redis[s]://[[username][:password]@][host][:port][/db-number],
|
||||||
|
# so for example redis://alice:foobared@awesome.redis.server:6380
|
||||||
|
# For saleor-platform, thats: `redis://redis:6379/1`
|
||||||
|
REDIS_URL=
|
||||||
|
|
||||||
APP_LOG_LEVEL=info
|
APP_LOG_LEVEL=info
|
||||||
|
|
||||||
# Local development variables. When developped locally with Saleor inside docker, these can be set to:
|
# Local development variables. When developped locally with Saleor inside docker, these can be set to:
|
||||||
|
@ -16,3 +22,4 @@ APP_LOG_LEVEL=info
|
||||||
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
|
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
|
||||||
APP_IFRAME_BASE_URL=
|
APP_IFRAME_BASE_URL=
|
||||||
APP_API_BASE_URL=
|
APP_API_BASE_URL=
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,73 @@
|
||||||
# saleor-app-emails-and-messages
|
# saleor-app-emails-and-messages
|
||||||
|
|
||||||
|
## 1.9.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||||
|
- 5dee65a: Updated dependencies:
|
||||||
|
- @graphql-codegen/cli@5.0.0
|
||||||
|
- 2e29699: Updated Sentry package
|
||||||
|
|
||||||
|
## 1.9.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- @saleor/apps-shared@1.9.0
|
||||||
|
- @saleor/apps-ui@1.2.0
|
||||||
|
|
||||||
|
## 1.9.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a81f061: Updated Macaw to pre-127
|
||||||
|
- fcc37e7: Remove clsx package from the projects no longer using it.
|
||||||
|
- Updated dependencies [2a1385b]
|
||||||
|
- Updated dependencies [a81f061]
|
||||||
|
- Updated dependencies [fcc37e7]
|
||||||
|
- @saleor/apps-shared@1.8.1
|
||||||
|
- @saleor/react-hook-form-macaw@0.2.5
|
||||||
|
- @saleor/apps-ui@1.1.8
|
||||||
|
|
||||||
|
## 1.9.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8b3d961: Updated Macaw UI to pre.118
|
||||||
|
- Updated dependencies [8b3d961]
|
||||||
|
- Updated dependencies [c50797e]
|
||||||
|
- @saleor/react-hook-form-macaw@0.2.4
|
||||||
|
- @saleor/apps-shared@1.8.0
|
||||||
|
- @saleor/apps-ui@1.1.7
|
||||||
|
|
||||||
|
## 1.9.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3002354: Added error logging for exceptions thrown at tRPC routes.
|
||||||
|
|
||||||
|
## 1.9.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b8c5283: Changed capitalization of the name SendGrid in the UI.
|
||||||
|
|
||||||
|
## 1.9.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 261957f: Updated dependencies: vite, vitest, eslint, prettier, dotenv, turbo, syncpack, changesets, lint staged
|
||||||
|
- Updated dependencies [261957f]
|
||||||
|
- @saleor/react-hook-form-macaw@0.2.3
|
||||||
|
- @saleor/apps-shared@1.7.6
|
||||||
|
- @saleor/apps-ui@1.1.6
|
||||||
|
|
||||||
## 1.9.3
|
## 1.9.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
@ -53,7 +121,7 @@
|
||||||
|
|
||||||
Added message to interface for users with missing permission or Saleor version without the support for this event.
|
Added message to interface for users with missing permission or Saleor version without the support for this event.
|
||||||
|
|
||||||
- e1980aa: Added validation for Sendgrid events form. Enabling event without a template is no longer allowed to avoid misconfiguration and undelivered emails.
|
- e1980aa: Added validation for SendGrid events form. Enabling event without a template is no longer allowed to avoid misconfiguration and undelivered emails.
|
||||||
- 6299e06: Update @saleor/app-sdk to 0.41.0
|
- 6299e06: Update @saleor/app-sdk to 0.41.0
|
||||||
- bda814b: Fixed issue with SMTP provider not sending emails on some ports.
|
- bda814b: Fixed issue with SMTP provider not sending emails on some ports.
|
||||||
- 78670ce: Changed formatting in the debugging logs to be more concise.
|
- 78670ce: Changed formatting in the debugging logs to be more concise.
|
||||||
|
@ -99,9 +167,9 @@
|
||||||
|
|
||||||
- a8834a1: Unified graphql version to 16.6
|
- a8834a1: Unified graphql version to 16.6
|
||||||
- cce2fbc: Improved layout spacing across the application.
|
- cce2fbc: Improved layout spacing across the application.
|
||||||
- e106ab9: Added improved descriptions in the Sendgrid configuration views.
|
- e106ab9: Added improved descriptions in the SendGrid configuration views.
|
||||||
- a8834a1: Unified graphql codegen packages
|
- a8834a1: Unified graphql codegen packages
|
||||||
- e106ab9: Added improved descriptions in the Sendgrid configuration views.
|
- e106ab9: Added improved descriptions in the SendGrid configuration views.
|
||||||
- a8834a1: Removed unnecessary duplicated dependencies from apps and moved them to shared and root (types, eslint rules)
|
- a8834a1: Removed unnecessary duplicated dependencies from apps and moved them to shared and root (types, eslint rules)
|
||||||
- a8834a1: Updated dev dependencies - Typescript, Eslint and Turborepo
|
- a8834a1: Updated dev dependencies - Typescript, Eslint and Turborepo
|
||||||
- 928c727: Updated @saleor/macaw-ui to 0.8.0-pre.95. This version introduces change in spacing scale, so there may be slight changes in spacing
|
- 928c727: Updated @saleor/macaw-ui to 0.8.0-pre.95. This version introduces change in spacing scale, so there may be slight changes in spacing
|
||||||
|
@ -217,7 +285,7 @@
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|
||||||
- 14ac614: Enable Sendgrid support
|
- 14ac614: Enable SendGrid support
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "saleor-app-emails-and-messages",
|
"name": "saleor-app-emails-and-messages",
|
||||||
"version": "1.9.3",
|
"version": "1.9.10",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm generate && next build",
|
"build": "pnpm generate && next build",
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||||
|
@ -12,25 +12,24 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.3.1",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@monaco-editor/react": "^4.4.6",
|
||||||
"@saleor/app-sdk": "0.41.1",
|
"@saleor/app-sdk": "0.43.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sendgrid/client": "^7.7.0",
|
"@sendgrid/client": "^7.7.0",
|
||||||
"@sendgrid/mail": "^7.7.0",
|
"@sendgrid/mail": "^7.7.0",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.67.0",
|
||||||
"@tanstack/react-query": "4.29.19",
|
"@tanstack/react-query": "4.29.19",
|
||||||
"@trpc/client": "10.34.0",
|
"@trpc/client": "10.38.1",
|
||||||
"@trpc/next": "10.34.0",
|
"@trpc/next": "10.38.1",
|
||||||
"@trpc/react-query": "10.34.0",
|
"@trpc/react-query": "10.38.1",
|
||||||
"@trpc/server": "10.34.0",
|
"@trpc/server": "10.38.1",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.4",
|
||||||
"clsx": "^1.2.1",
|
"dotenv": "^16.3.1",
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"graphql": "16.7.1",
|
"graphql": "16.7.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
|
@ -47,12 +46,12 @@
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.4.8",
|
||||||
"vitest": "0.31.3",
|
"vitest": "0.34.1",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "4.0.1",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/schema-ast": "4.0.0",
|
"@graphql-codegen/schema-ast": "4.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
|
@ -60,14 +59,14 @@
|
||||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
"@testing-library/react-hooks": "^8.0.1",
|
||||||
"@types/html-to-text": "^9.0.0",
|
"@types/html-to-text": "^9.0.0",
|
||||||
"@types/mjml": "^4.7.0",
|
"@types/mjml": "^4.7.0",
|
||||||
"@types/nodemailer": "^6.4.7",
|
"@types/nodemailer": "^6.4.7",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,7 +28,7 @@ const NoExistingConfigurations = () => {
|
||||||
type ProviderType = "sendgrid" | "smtp";
|
type ProviderType = "sendgrid" | "smtp";
|
||||||
|
|
||||||
const providerLabels: Record<ProviderType, string> = {
|
const providerLabels: Record<ProviderType, string> = {
|
||||||
sendgrid: "Sendgrid",
|
sendgrid: "SendGrid",
|
||||||
smtp: "SMTP",
|
smtp: "SMTP",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ export const sendEventMessages = async ({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sendgridStatus?.errors.length) {
|
if (sendgridStatus?.errors.length) {
|
||||||
logger.error("Sendgrid errors");
|
logger.error("SendGrid errors");
|
||||||
logger.error(sendgridStatus?.errors);
|
logger.error(sendgridStatus?.errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const sendSendgrid = async ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("Sending an email using Sendgrid");
|
logger.debug("Sending an email using SendGrid");
|
||||||
|
|
||||||
const { template } = eventSettings;
|
const { template } = eventSettings;
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ export const sendSendgrid = async ({
|
||||||
});
|
});
|
||||||
logger.debug("Email has been send");
|
logger.debug("Email has been send");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("The Sendgrid API returned an error");
|
logger.error("The SendGrid API returned an error");
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return { errors: [{ message: error.message }] };
|
return { errors: [{ message: error.message }] };
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const fetchTemplates =
|
||||||
async () => {
|
async () => {
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"The Sendgrid API key has not been set up yet. Skipping fetching available templates."
|
"The SendGrid API key has not been set up yet. Skipping fetching available templates."
|
||||||
);
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ export const fetchTemplates =
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Could not fetch available Sendgrid templates");
|
console.error("Could not fetch available SendGrid templates");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -37,7 +37,7 @@ export const fetchTemplates =
|
||||||
|
|
||||||
return templates;
|
return templates;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not parse the response from Sendgrid", e);
|
console.error("Could not parse the response from SendGrid", e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ export const fetchSenders =
|
||||||
async () => {
|
async () => {
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"The Sendgrid API key has not been set up yet. Skipping fetching available senders ."
|
"The SendGrid API key has not been set up yet. Skipping fetching available senders ."
|
||||||
);
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ export const fetchSenders =
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Could not fetch available Sendgrid senders");
|
console.error("Could not fetch available SendGrid senders");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -76,7 +76,7 @@ export const fetchSenders =
|
||||||
|
|
||||||
return senders;
|
return senders;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not parse the response from Sendgrid", e);
|
console.error("Could not parse the response from SendGrid", e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const ApiConnectionSection = ({ configuration }: ApiConnectionSectionProp
|
||||||
label="API Key"
|
label="API Key"
|
||||||
name="apiKey"
|
name="apiKey"
|
||||||
control={control}
|
control={control}
|
||||||
helperText="The API key can be generated in your Sendgrid dashboard"
|
helperText="The API key can be generated in your SendGrid dashboard"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Text } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
export const SendgridApiKeyDescriptionText = () => (
|
export const SendgridApiKeyDescriptionText = () => (
|
||||||
<Text as="p">
|
<Text as="p">
|
||||||
The API keys can be found at your Sendgrid dashboard, in the Settings menu. You can find more
|
The API keys can be found at your SendGrid dashboard, in the Settings menu. You can find more
|
||||||
information in the{" "}
|
information in the{" "}
|
||||||
<TextLink href="https://docs.sendgrid.com/ui/account-and-settings/api-keys" newTab={true}>
|
<TextLink href="https://docs.sendgrid.com/ui/account-and-settings/api-keys" newTab={true}>
|
||||||
documentation
|
documentation
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
||||||
title="Events"
|
title="Events"
|
||||||
description={
|
description={
|
||||||
<Text as="p">
|
<Text as="p">
|
||||||
Choose which Saleor events should send emails via Sendgrid. You can create and modify your
|
Choose which Saleor events should send emails via SendGrid. You can create and modify your
|
||||||
templates in the{" "}
|
templates in the{" "}
|
||||||
<TextLink href="https://mc.sendgrid.com/dynamic-templates" newTab={true}>
|
<TextLink href="https://mc.sendgrid.com/dynamic-templates" newTab={true}>
|
||||||
Sendgrid dashboard
|
Sendgrid dashboard
|
||||||
|
|
|
@ -7,5 +7,5 @@ interface SendgridLogoProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SendgridLogo = ({ height, width }: SendgridLogoProps) => {
|
export const SendgridLogo = ({ height, width }: SendgridLogoProps) => {
|
||||||
return <Image alt="Sendgrid logo" src={sendgrid} height={height} width={width} />;
|
return <Image alt="SendGrid logo" src={sendgrid} height={height} width={width} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const SendgridSenderSection = ({ configuration }: SendgridSenderSectionPr
|
||||||
<Text as="p">
|
<Text as="p">
|
||||||
Authenticating the sender is required to send emails. Configure your sender in{" "}
|
Authenticating the sender is required to send emails. Configure your sender in{" "}
|
||||||
<TextLink href="https://app.sendgrid.com/settings/sender_auth" newTab={true}>
|
<TextLink href="https://app.sendgrid.com/settings/sender_auth" newTab={true}>
|
||||||
Sendgrid dashboard
|
SendGrid dashboard
|
||||||
</TextLink>{" "}
|
</TextLink>{" "}
|
||||||
and choose it from the list.
|
and choose it from the list.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
import * as trpcNext from "@trpc/server/adapters/next";
|
||||||
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
||||||
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
|
||||||
|
const logger = createLogger({ name: "tRPC error" });
|
||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
export default trpcNext.createNextApiHandler({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: createTrpcContext,
|
createContext: createTrpcContext,
|
||||||
|
onError: ({ path, error }) => {
|
||||||
|
if (error.code === "INTERNAL_SERVER_ERROR") {
|
||||||
|
logger.error(error, `${path} returned error:`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(error, `${path} returned error:`);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,9 +28,9 @@ const ChooseProviderPage: NextPage = () => {
|
||||||
<SectionWithDescription title="Choose provider">
|
<SectionWithDescription title="Choose provider">
|
||||||
<Box display="grid" gridTemplateColumns={2} gap={3}>
|
<Box display="grid" gridTemplateColumns={2} gap={3}>
|
||||||
<ProviderSelectionBox
|
<ProviderSelectionBox
|
||||||
providerName="Sendgrid"
|
providerName="SendGrid"
|
||||||
providerLogo={<SendgridLogo height={20} width={20} />}
|
providerLogo={<SendgridLogo height={20} width={20} />}
|
||||||
providerDescription="Use dynamic templates created in Sendgrid dashboard to send messages. Event data will be forwarded to Sendgrid."
|
providerDescription="Use dynamic templates created in SendGrid dashboard to send messages. Event data will be forwarded to SendGrid."
|
||||||
onClick={() => push(sendgridUrls.newConfiguration())}
|
onClick={() => push(sendgridUrls.newConfiguration())}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ const LoadingView = () => {
|
||||||
<BasicLayout
|
<BasicLayout
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: "Configuration", href: appUrls.configuration() },
|
{ name: "Configuration", href: appUrls.configuration() },
|
||||||
{ name: "Sendgrid provider" },
|
{ name: "SendGrid provider" },
|
||||||
{ name: "..." },
|
{ name: "..." },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -31,7 +31,7 @@ const NotFoundView = () => {
|
||||||
<BasicLayout
|
<BasicLayout
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: "Configuration", href: appUrls.configuration() },
|
{ name: "Configuration", href: appUrls.configuration() },
|
||||||
{ name: "Sendgrid provider" },
|
{ name: "SendGrid provider" },
|
||||||
{ name: "Not found" },
|
{ name: "Not found" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -78,13 +78,13 @@ const EditSendgridConfigurationPage: NextPage = () => {
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: "Configuration", href: appUrls.configuration() },
|
{ name: "Configuration", href: appUrls.configuration() },
|
||||||
{
|
{
|
||||||
name: `Sendgrid: ${configuration.name}`,
|
name: `SendGrid: ${configuration.name}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Box display="grid" gridTemplateColumns={{ desktop: 3, mobile: 1 }}>
|
<Box display="grid" gridTemplateColumns={{ desktop: 3, mobile: 1 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Text>Connect Sendgrid with Saleor.</Text>
|
<Text>Connect SendGrid with Saleor.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<SendgridBasicInformationSection configuration={configuration} />
|
<SendgridBasicInformationSection configuration={configuration} />
|
||||||
|
|
|
@ -43,16 +43,16 @@ const NewSendgridConfigurationPage: NextPage = () => {
|
||||||
breadcrumbs={[
|
breadcrumbs={[
|
||||||
{ name: "Configuration", href: appUrls.configuration() },
|
{ name: "Configuration", href: appUrls.configuration() },
|
||||||
{ name: "Add provider" },
|
{ name: "Add provider" },
|
||||||
{ name: "Sendgrid" },
|
{ name: "SendGrid" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Box display="grid" gridTemplateColumns={{ desktop: 3, mobile: 1 }}>
|
<Box display="grid" gridTemplateColumns={{ desktop: 3, mobile: 1 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Text>Connect Sendgrid with Saleor.</Text>
|
<Text>Connect SendGrid with Saleor.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<SectionWithDescription
|
<SectionWithDescription
|
||||||
title="Connect Sendgrid"
|
title="Connect SendGrid"
|
||||||
description={
|
description={
|
||||||
<Box display="flex" flexDirection="column" gap={2}>
|
<Box display="flex" flexDirection="column" gap={2}>
|
||||||
<ConfigurationNameDescriptionText />
|
<ConfigurationNameDescriptionText />
|
||||||
|
@ -80,7 +80,7 @@ const NewSendgridConfigurationPage: NextPage = () => {
|
||||||
name="apiKey"
|
name="apiKey"
|
||||||
control={control}
|
control={control}
|
||||||
label="API key"
|
label="API key"
|
||||||
helperText={"The API key can be generated in your Sendgrid dashboard"}
|
helperText={"The API key can be generated in your SendGrid dashboard"}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<BoxFooter>
|
<BoxFooter>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
|
import { APL, FileAPL, RedisAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
|
||||||
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
|
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
|
||||||
|
|
||||||
const aplType = process.env.APL ?? "file";
|
const aplType = process.env.APL ?? "file";
|
||||||
|
@ -6,6 +6,12 @@ const aplType = process.env.APL ?? "file";
|
||||||
export let apl: APL;
|
export let apl: APL;
|
||||||
|
|
||||||
switch (aplType) {
|
switch (aplType) {
|
||||||
|
case "redis": {
|
||||||
|
if (!process.env.REDIS_URL) throw new Error("Missing redis url");
|
||||||
|
if (!process.env.APP_API_BASE_URL)
|
||||||
|
throw new Error("Redis relies on APP_API_BASE_URL to store keys, please set env variable");
|
||||||
|
apl = new RedisAPL(new URL(process.env.REDIS_URL), process.env.APP_API_BASE_URL);
|
||||||
|
}
|
||||||
case "upstash":
|
case "upstash":
|
||||||
apl = new UpstashAPL();
|
apl = new UpstashAPL();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": ["//"],
|
"extends": [
|
||||||
|
"//"
|
||||||
|
],
|
||||||
"$schema": "https://turbo.build/schema.json",
|
"$schema": "https://turbo.build/schema.json",
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"build": {
|
"build": {
|
||||||
|
@ -21,7 +23,8 @@
|
||||||
"NEXT_PUBLIC_SENTRY_DSN",
|
"NEXT_PUBLIC_SENTRY_DSN",
|
||||||
"SENTRY_ENVIRONMENT",
|
"SENTRY_ENVIRONMENT",
|
||||||
"APP_IFRAME_BASE_URL",
|
"APP_IFRAME_BASE_URL",
|
||||||
"APP_API_BASE_URL"
|
"APP_API_BASE_URL",
|
||||||
|
"REDIS_URL"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,63 @@
|
||||||
# saleor-app-invoices
|
# saleor-app-invoices
|
||||||
|
|
||||||
|
## 1.16.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 4aee4e1: Replace text "loading" messages with skeletons
|
||||||
|
- 4aee4e1: Redesigned app layout. Now app uses shared sections as other apps.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||||
|
- 5dee65a: Updated dependencies:
|
||||||
|
- @graphql-codegen/cli@5.0.0
|
||||||
|
- 2e29699: Updated Sentry package
|
||||||
|
|
||||||
|
## 1.15.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- @saleor/apps-shared@1.9.0
|
||||||
|
|
||||||
|
## 1.15.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a81f061: Updated Macaw to pre-127
|
||||||
|
- fcc37e7: Remove clsx package from the projects no longer using it.
|
||||||
|
- Updated dependencies [2a1385b]
|
||||||
|
- Updated dependencies [a81f061]
|
||||||
|
- Updated dependencies [fcc37e7]
|
||||||
|
- @saleor/apps-shared@1.8.1
|
||||||
|
|
||||||
|
## 1.15.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8b3d961: Updated Macaw UI to pre.118
|
||||||
|
- Updated dependencies [8b3d961]
|
||||||
|
- Updated dependencies [c50797e]
|
||||||
|
- @saleor/apps-shared@1.8.0
|
||||||
|
|
||||||
|
## 1.15.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3002354: Added error logging for exceptions thrown at tRPC routes.
|
||||||
|
|
||||||
|
## 1.15.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 261957f: Updated dependencies: vite, vitest, eslint, prettier, dotenv, turbo, syncpack, changesets, lint staged
|
||||||
|
- Updated dependencies [261957f]
|
||||||
|
- @saleor/apps-shared@1.7.6
|
||||||
|
|
||||||
## 1.15.2
|
## 1.15.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
const { withSentryConfig } = require("@sentry/nextjs");
|
const { withSentryConfig } = require("@sentry/nextjs");
|
||||||
|
|
||||||
const isSentryPropertiesInEnvironment = Boolean(
|
const isSentryPropertiesInEnvironment = Boolean(
|
||||||
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,
|
||||||
);
|
);
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
|
transpilePackages: [
|
||||||
|
"@saleor/apps-shared",
|
||||||
|
"@saleor/apps-ui",
|
||||||
|
"@saleor/react-hook-form-macaw",
|
||||||
|
"@saleor/trpc",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const configWithSentry = withSentryConfig(
|
const configWithSentry = withSentryConfig(
|
||||||
|
@ -23,9 +28,7 @@ const configWithSentry = withSentryConfig(
|
||||||
tunnelRoute: "/monitoring",
|
tunnelRoute: "/monitoring",
|
||||||
hideSourceMaps: true,
|
hideSourceMaps: true,
|
||||||
disableLogger: true,
|
disableLogger: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;
|
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "saleor-app-invoices",
|
"name": "saleor-app-invoices",
|
||||||
"version": "1.15.2",
|
"version": "1.16.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm generate && next build",
|
"build": "pnpm generate && next build",
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||||
|
@ -12,20 +12,21 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.1.0",
|
"@hookform/resolvers": "^3.3.1",
|
||||||
"@saleor/app-sdk": "0.41.1",
|
"@saleor/app-sdk": "0.43.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||||
|
"@saleor/trpc": "workspace:*",
|
||||||
|
"@sentry/nextjs": "7.67.0",
|
||||||
"@tanstack/react-query": "4.29.19",
|
"@tanstack/react-query": "4.29.19",
|
||||||
"@trpc/client": "10.34.0",
|
"@trpc/client": "10.38.1",
|
||||||
"@trpc/next": "10.34.0",
|
"@trpc/next": "10.38.1",
|
||||||
"@trpc/react-query": "10.34.0",
|
"@trpc/react-query": "10.38.1",
|
||||||
"@trpc/server": "10.34.0",
|
"@trpc/server": "10.38.1",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"@web-std/file": "^3.0.2",
|
"@web-std/file": "^3.0.2",
|
||||||
"clsx": "^1.2.1",
|
"eslint": "8.46.0",
|
||||||
"eslint": "8.44.0",
|
|
||||||
"graphql": "16.7.1",
|
"graphql": "16.7.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"microinvoice": "^1.0.6",
|
"microinvoice": "^1.0.6",
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "4.0.1",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/schema-ast": "4.0.0",
|
"@graphql-codegen/schema-ast": "4.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
|
@ -52,15 +53,14 @@
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"@types/rimraf": "^3.0.2",
|
"@types/rimraf": "^3.0.2",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.4",
|
||||||
"@vitest/coverage-c8": "^0.28.4",
|
"dotenv": "^16.3.1",
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"jsdom": "^20.0.3",
|
"jsdom": "^20.0.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "5.1.6",
|
"typescript": "5.1.6",
|
||||||
"vite": "4.3.9",
|
"vite": "4.4.8",
|
||||||
"vitest": "0.31.3"
|
"vitest": "0.34.1"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"saleor": {
|
"saleor": {
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
import { useTheme } from "@saleor/macaw-ui/next";
|
|
||||||
import { memo, useEffect } from "react";
|
|
||||||
|
|
||||||
// todo move to shared
|
|
||||||
export function ThemeSynchronizer() {
|
|
||||||
const { appBridgeState } = useAppBridge();
|
|
||||||
const { setTheme } = useTheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!setTheme || !appBridgeState?.theme) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appBridgeState.theme === "light") {
|
|
||||||
setTheme("defaultLight");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appBridgeState.theme === "dark") {
|
|
||||||
setTheme("defaultDark");
|
|
||||||
}
|
|
||||||
}, [appBridgeState?.theme, setTheme]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -6,6 +6,7 @@ import { SimpleGraphqlClient } from "../metadata-manager";
|
||||||
export const mockMetadataManager = {
|
export const mockMetadataManager = {
|
||||||
set: vi.fn().mockImplementation(async () => {}),
|
set: vi.fn().mockImplementation(async () => {}),
|
||||||
get: vi.fn().mockImplementation(async () => {}),
|
get: vi.fn().mockImplementation(async () => {}),
|
||||||
|
delete: vi.fn().mockImplementation(async () => {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createSettingsManager = (client: SimpleGraphqlClient): SettingsManager => {
|
export const createSettingsManager = (client: SimpleGraphqlClient): SettingsManager => {
|
||||||
|
|
|
@ -53,6 +53,8 @@ describe("appConfigurationRouter", function () {
|
||||||
token: "TOKEN",
|
token: "TOKEN",
|
||||||
saleorApiUrl: "http://localhost:8000/graphql/",
|
saleorApiUrl: "http://localhost:8000/graphql/",
|
||||||
appId: "app",
|
appId: "app",
|
||||||
|
ssr: true,
|
||||||
|
baseUrl: "localhost:3000",
|
||||||
})
|
})
|
||||||
.upsertChannelOverride({
|
.upsertChannelOverride({
|
||||||
channelSlug: "test",
|
channelSlug: "test",
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
|
||||||
import React, { useCallback, useEffect } from "react";
|
|
||||||
import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
|
|
||||||
import { SellerAddress } from "../address";
|
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
|
||||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
|
import { SellerAddress } from "../address";
|
||||||
import { AddressV2Schema, AddressV2Shape } from "../schema-v2/app-config-schema.v2";
|
import { AddressV2Schema, AddressV2Shape } from "../schema-v2/app-config-schema.v2";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -57,12 +58,29 @@ export const AddressForm = (props: Props & InnerFormProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<Layout.AppSectionCard
|
||||||
|
as="form"
|
||||||
|
footer={
|
||||||
|
<ButtonsBox>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
props.onCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text color={"textNeutralSubdued"}>Cancel</Text>
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="primary">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</ButtonsBox>
|
||||||
|
}
|
||||||
onSubmit={handleSubmit((data, event) => {
|
onSubmit={handleSubmit((data, event) => {
|
||||||
return props.onSubmit(data);
|
return props.onSubmit(data);
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box display={"grid"} gap={3} marginBottom={9}>
|
<Box display={"grid"} gap={3}>
|
||||||
{fieldsBlock1.map((fieldName) => (
|
{fieldsBlock1.map((fieldName) => (
|
||||||
<Controller
|
<Controller
|
||||||
key={fieldName}
|
key={fieldName}
|
||||||
|
@ -128,21 +146,7 @@ export const AddressForm = (props: Props & InnerFormProps) => {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
<Box display={"grid"} justifyContent={"flex-end"} gap={1.5} gridAutoFlow={"column"}>
|
</Layout.AppSectionCard>
|
||||||
<Button
|
|
||||||
variant="tertiary"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
props.onCancel();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text color={"textNeutralSubdued"}>Cancel</Text>
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" variant="primary">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -164,9 +168,6 @@ export const ConnectedAddressForm = (props: Props) => {
|
||||||
|
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
||||||
const addressData =
|
|
||||||
channelOverrideConfigQuery.data && channelOverrideConfigQuery.data[props.channelSlug];
|
|
||||||
|
|
||||||
const submitHandler = useCallback(
|
const submitHandler = useCallback(
|
||||||
async (data: AddressV2Shape) => {
|
async (data: AddressV2Shape) => {
|
||||||
return upsertConfigMutation.mutate({
|
return upsertConfigMutation.mutate({
|
||||||
|
@ -174,7 +175,7 @@ export const ConnectedAddressForm = (props: Props) => {
|
||||||
channelSlug: props.channelSlug,
|
channelSlug: props.channelSlug,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[props.channelSlug, upsertConfigMutation]
|
[props.channelSlug, upsertConfigMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onCancelHandler = useCallback(() => {
|
const onCancelHandler = useCallback(() => {
|
||||||
|
@ -182,7 +183,7 @@ export const ConnectedAddressForm = (props: Props) => {
|
||||||
}, [push]);
|
}, [push]);
|
||||||
|
|
||||||
if (channelOverrideConfigQuery.isLoading) {
|
if (channelOverrideConfigQuery.isLoading) {
|
||||||
return <Text color={"textNeutralSubdued"}>Loading</Text>;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
import { Box, Text } from "@saleor/macaw-ui/next";
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
import { DefaultShopAddress } from "../../shop-info/ui/default-shop-address";
|
import { DefaultShopAddress } from "../../shop-info/ui/default-shop-address";
|
||||||
import { AppSection } from "../../ui/AppSection";
|
|
||||||
import { PerChannelConfigList } from "../../channels/ui/per-channel-config-list";
|
import { PerChannelConfigList } from "../../channels/ui/per-channel-config-list";
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import { Layout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
export const AppConfigView = () => {
|
export const AppConfigView = () => {
|
||||||
const { appBridge } = useAppBridge();
|
const { appBridge } = useAppBridge();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box
|
<Text as={"h1"} variant={"hero"} marginBottom={5}>
|
||||||
display={"grid"}
|
Configuration
|
||||||
justifyContent={"space-between"}
|
</Text>
|
||||||
__gridTemplateColumns={"400px 400px"}
|
<Text as={"p"} marginBottom={1.5}>
|
||||||
gap={10}
|
The Invoices App will generate invoices for each order, for which{" "}
|
||||||
__marginBottom={"200px"}
|
<code>INVOICE_REQUESTED</code> event will be triggered
|
||||||
>
|
</Text>
|
||||||
<Box>
|
<Layout.AppSection
|
||||||
<Text as={"h1"} variant={"hero"} marginBottom={5}>
|
marginTop={10}
|
||||||
Configuration
|
heading={"Default address of the shop"}
|
||||||
</Text>
|
sideContent={
|
||||||
<Text as={"p"} marginBottom={1.5}>
|
|
||||||
The Invoices App will generate invoices for each order, for which{" "}
|
|
||||||
<code>INVOICE_REQUESTED</code> event will be triggered
|
|
||||||
</Text>
|
|
||||||
<Text as={"p"} marginBottom={1.5}>
|
<Text as={"p"} marginBottom={1.5}>
|
||||||
By default it will use{" "}
|
By default it will use{" "}
|
||||||
<a
|
<a
|
||||||
|
@ -32,7 +28,7 @@ export const AppConfigView = () => {
|
||||||
appBridge?.dispatch(
|
appBridge?.dispatch(
|
||||||
actions.Redirect({
|
actions.Redirect({
|
||||||
to: "/site-settings",
|
to: "/site-settings",
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -40,22 +36,25 @@ export const AppConfigView = () => {
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
address, but each channel can be configured separately
|
address, but each channel can be configured separately
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
}
|
||||||
<Box>
|
>
|
||||||
<DefaultShopAddress />
|
<DefaultShopAddress />
|
||||||
</Box>
|
</Layout.AppSection>
|
||||||
</Box>
|
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
includePadding={true}
|
marginTop={10}
|
||||||
heading={"Shop address per channel"}
|
heading={"Shop address per channel"}
|
||||||
mainContent={<PerChannelConfigList />}
|
|
||||||
sideContent={
|
sideContent={
|
||||||
<Text>
|
<Text>
|
||||||
Configure custom billing address for each channel. If not set, default shop address will
|
Configure custom billing address for each channel. If not set, default shop address will
|
||||||
be used
|
be used
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<Layout.AppSectionCard>
|
||||||
|
<PerChannelConfigList />
|
||||||
|
</Layout.AppSectionCard>
|
||||||
|
</Layout.AppSection>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Box, ChevronRightIcon, Text, Button } from "@saleor/macaw-ui/next";
|
import { Box, ChevronRightIcon, Text, Button } from "@saleor/macaw-ui/next";
|
||||||
import { AppSection } from "../../ui/AppSection";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { ConnectedAddressForm } from "../ui/address-form";
|
import { ConnectedAddressForm } from "../ui/address-form";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
import { Layout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
export const ChannelConfigView = () => {
|
export const ChannelConfigView = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -15,7 +15,7 @@ export const ChannelConfigView = () => {
|
||||||
const { notifySuccess } = useDashboardNotification();
|
const { notifySuccess } = useDashboardNotification();
|
||||||
|
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
return null;
|
return null; // TODO: error
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -29,10 +29,9 @@ export const ChannelConfigView = () => {
|
||||||
<Text>{channel}</Text>
|
<Text>{channel}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<AppSection
|
<Layout.AppSection
|
||||||
includePadding={true}
|
includePadding={true}
|
||||||
heading={"Shop address per channel"}
|
heading={"Shop address per channel"}
|
||||||
mainContent={<ConnectedAddressForm channelSlug={channel as string} />}
|
|
||||||
sideContent={
|
sideContent={
|
||||||
<Box>
|
<Box>
|
||||||
<Text marginBottom={5} as={"p"}>
|
<Text marginBottom={5} as={"p"}>
|
||||||
|
@ -51,7 +50,9 @@ export const ChannelConfigView = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<ConnectedAddressForm channelSlug={channel as string} />
|
||||||
|
</Layout.AppSection>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Box, Text, Chip, Button } from "@saleor/macaw-ui/next";
|
import { Box, Text, Chip, Button } from "@saleor/macaw-ui/next";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
const defaultAddressChip = (
|
const defaultAddressChip = (
|
||||||
<Chip __display={"inline-block"} size={"large"}>
|
<Chip __display={"inline-block"} size={"large"}>
|
||||||
|
@ -17,7 +18,7 @@ export const PerChannelConfigList = () => {
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
||||||
if (shopChannelsQuery.isLoading || channelsOverridesQuery.isLoading) {
|
if (shopChannelsQuery.isLoading || channelsOverridesQuery.isLoading) {
|
||||||
return <Text color={"textNeutralSubdued"}>Loading...</Text>;
|
return <SkeletonLayout.Section />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderChannelAddress = (slug: string) => {
|
const renderChannelAddress = (slug: string) => {
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
import { hashInvoiceFilename } from "./hash-invoice-filename";
|
import { hashInvoiceFilename } from "./hash-invoice-filename";
|
||||||
|
|
||||||
vi.mock("crypto", () => ({
|
vi.mock("crypto", async () => {
|
||||||
randomUUID() {
|
const actual = (await vi.importActual("crypto")) as Crypto;
|
||||||
return "RANDOM_UUID_MOCK";
|
|
||||||
},
|
return {
|
||||||
}));
|
default: {
|
||||||
|
...actual,
|
||||||
|
randomUUID() {
|
||||||
|
return "RANDOM_UUID_MOCK";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe("hashInvoiceFilename", () => {
|
describe("hashInvoiceFilename", () => {
|
||||||
it("Creates hashed invoice name", () => {
|
it("Creates hashed invoice name", () => {
|
||||||
expect(hashInvoiceFilename("1/12/2022", "1234-xxxx-zzzz-1234")).toBe(
|
expect(hashInvoiceFilename("1/12/2022", "1234-xxxx-zzzz-1234")).toBe(
|
||||||
"1/12/2022_1234-xxxx-zzzz-1234_RANDOM_UUID_MOCK"
|
"1/12/2022_1234-xxxx-zzzz-1234_RANDOM_UUID_MOCK",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,30 +2,31 @@ import { Box, Text, Button } from "@saleor/macaw-ui/next";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
|
||||||
|
|
||||||
const Wrapper = ({ children }: PropsWithChildren<{}>) => {
|
const Wrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||||
const { appBridge } = useAppBridge();
|
const { appBridge } = useAppBridge();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Layout.AppSectionCard
|
||||||
<Box display={"flex"} justifyContent={"space-between"} marginBottom={5}>
|
footer={
|
||||||
<Text variant={"heading"}>Default address of the shop</Text>
|
<ButtonsBox>
|
||||||
<Button
|
<Button
|
||||||
size={"small"}
|
onClick={() => {
|
||||||
variant={"tertiary"}
|
appBridge?.dispatch(
|
||||||
onClick={() => {
|
actions.Redirect({
|
||||||
appBridge?.dispatch(
|
to: "/site-settings",
|
||||||
actions.Redirect({
|
}),
|
||||||
to: "/site-settings",
|
);
|
||||||
})
|
}}
|
||||||
);
|
>
|
||||||
}}
|
Edit
|
||||||
>
|
</Button>
|
||||||
<Text color={"textNeutralSubdued"}>Edit</Text>
|
</ButtonsBox>
|
||||||
</Button>
|
}
|
||||||
</Box>
|
>
|
||||||
<Box>{children}</Box>
|
<Box>{children}</Box>
|
||||||
</Box>
|
</Layout.AppSectionCard>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ export const DefaultShopAddress = () => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Text color={"textNeutralSubdued"}>Loading...</Text>
|
<SkeletonLayout.Section />
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +71,9 @@ export const DefaultShopAddress = () => {
|
||||||
if (data && data.companyAddress) {
|
if (data && data.companyAddress) {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
|
<Text as="p" marginBottom={4} variant="caption">
|
||||||
|
This address will be used if custom address is not set for channel
|
||||||
|
</Text>
|
||||||
<Text size={"small"} as={"p"}>
|
<Text size={"small"} as={"p"}>
|
||||||
{data.companyAddress.companyName}
|
{data.companyAddress.companyName}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -1,34 +1,13 @@
|
||||||
import { httpBatchLink } from "@trpc/client";
|
|
||||||
import { createTRPCNext } from "@trpc/next";
|
import { createTRPCNext } from "@trpc/next";
|
||||||
|
|
||||||
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
|
import { createHttpBatchLink } from "@saleor/trpc";
|
||||||
import { appBridgeInstance } from "../../pages/_app";
|
import { appBridgeInstance } from "../../pages/_app";
|
||||||
import { AppRouter } from "./trpc-app-router";
|
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>({
|
export const trpcClient = createTRPCNext<AppRouter>({
|
||||||
config({ ctx }) {
|
config() {
|
||||||
return {
|
return {
|
||||||
links: [
|
links: [createHttpBatchLink(appBridgeInstance)],
|
||||||
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 } } },
|
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC } from "@trpc/server";
|
||||||
import { TrpcContext } from "./trpc-context";
|
|
||||||
import { Permission } from "@saleor/app-sdk/types";
|
import { Permission } from "@saleor/app-sdk/types";
|
||||||
|
import { TrpcContext } from "@saleor/trpc";
|
||||||
|
|
||||||
interface Meta {
|
interface Meta {
|
||||||
requiredClientPermissions?: Permission[];
|
requiredClientPermissions?: Permission[];
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
// todo move to shared
|
|
||||||
export const AppSection = ({
|
|
||||||
heading,
|
|
||||||
sideContent,
|
|
||||||
mainContent,
|
|
||||||
includePadding = false,
|
|
||||||
...props
|
|
||||||
}: PropsWithBox<{
|
|
||||||
heading: string;
|
|
||||||
sideContent?: ReactNode;
|
|
||||||
mainContent: ReactNode;
|
|
||||||
includePadding?: boolean;
|
|
||||||
}>) => {
|
|
||||||
return (
|
|
||||||
<Box as="section" __gridTemplateColumns={"400px auto"} display={"grid"} gap={10} {...props}>
|
|
||||||
<Box>
|
|
||||||
<Text as="h2" variant={"heading"} size={"large"} marginBottom={1.5}>
|
|
||||||
{heading}
|
|
||||||
</Text>
|
|
||||||
{sideContent}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
borderStyle={"solid"}
|
|
||||||
borderColor={"neutralPlain"}
|
|
||||||
borderWidth={1}
|
|
||||||
padding={includePadding ? 5 : 0}
|
|
||||||
borderRadius={4}
|
|
||||||
>
|
|
||||||
{mainContent}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -6,12 +6,11 @@ import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||||
import React, { ReactElement } from "react";
|
import React, { ReactElement } from "react";
|
||||||
import { AppProps } from "next/app";
|
import { AppProps } from "next/app";
|
||||||
|
|
||||||
import { NoSSRWrapper } from "@saleor/apps-shared";
|
import { NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
|
||||||
import { trpcClient } from "../modules/trpc/trpc-client";
|
import { trpcClient } from "../modules/trpc/trpc-client";
|
||||||
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { ThemeSynchronizer } from "../lib/theme-synchronizer";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure instance is a singleton.
|
* Ensure instance is a singleton.
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import * as trpcNext from "@trpc/server/adapters/next";
|
import * as trpcNext from "@trpc/server/adapters/next";
|
||||||
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
|
|
||||||
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
import { appRouter } from "../../../modules/trpc/trpc-app-router";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
import { createTrpcContext } from "@saleor/trpc";
|
||||||
|
|
||||||
|
const logger = createLogger({ name: "tRPC error" });
|
||||||
|
|
||||||
export default trpcNext.createNextApiHandler({
|
export default trpcNext.createNextApiHandler({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: createTrpcContext,
|
createContext: createTrpcContext,
|
||||||
|
onError: ({ path, error }) => {
|
||||||
|
if (error.code === "INTERNAL_SERVER_ERROR") {
|
||||||
|
logger.error(error, `${path} returned error:`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(error, `${path} returned error:`);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
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,8 +1,3 @@
|
||||||
body {
|
|
||||||
font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
|
||||||
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
|
@ -8,9 +8,5 @@ export default defineConfig({
|
||||||
environment: "jsdom",
|
environment: "jsdom",
|
||||||
setupFiles: "./src/setup-tests.ts",
|
setupFiles: "./src/setup-tests.ts",
|
||||||
css: false,
|
css: false,
|
||||||
coverage: {
|
|
||||||
provider: "c8",
|
|
||||||
reporter: ["text-summary", "cobertura"],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,57 @@
|
||||||
# saleor-app-klaviyo
|
# saleor-app-klaviyo
|
||||||
|
|
||||||
|
## 1.9.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 4aee4e1: Improved app layout to match modern style.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 5dee65a: Updated dependencies:
|
||||||
|
- @graphql-codegen/cli@5.0.0
|
||||||
|
- 2e29699: Updated Sentry package
|
||||||
|
- 4aee4e1: Fixed error where config couldn't be saved
|
||||||
|
|
||||||
|
## 1.8.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 30140ee: Improved some text typos.
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- Updated dependencies [e8660e8]
|
||||||
|
- @saleor/apps-shared@1.9.0
|
||||||
|
|
||||||
|
## 1.8.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- a81f061: Updated Macaw to pre-127
|
||||||
|
- fcc37e7: Remove clsx package from the projects no longer using it.
|
||||||
|
- Updated dependencies [2a1385b]
|
||||||
|
- Updated dependencies [a81f061]
|
||||||
|
- Updated dependencies [fcc37e7]
|
||||||
|
- @saleor/apps-shared@1.8.1
|
||||||
|
|
||||||
|
## 1.8.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8b3d961: Updated Macaw UI to pre.118
|
||||||
|
- Updated dependencies [8b3d961]
|
||||||
|
- Updated dependencies [c50797e]
|
||||||
|
- @saleor/apps-shared@1.8.0
|
||||||
|
|
||||||
|
## 1.8.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 261957f: Updated dependencies: vite, vitest, eslint, prettier, dotenv, turbo, syncpack, changesets, lint staged
|
||||||
|
- Updated dependencies [261957f]
|
||||||
|
- @saleor/apps-shared@1.7.6
|
||||||
|
|
||||||
## 1.8.2
|
## 1.8.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -36,7 +36,7 @@ Klaviyo app is based on App Template - you can check it [here](https://github.co
|
||||||
3. Install app dependencies with `pnpm i`
|
3. Install app dependencies with `pnpm i`
|
||||||
4. Start dev server `pnpm dev`
|
4. Start dev server `pnpm dev`
|
||||||
5. To install app in your Saleor Instance and expose dev server to it, run in the separate terminal `saleor app tunnel`
|
5. To install app in your Saleor Instance and expose dev server to it, run in the separate terminal `saleor app tunnel`
|
||||||
6. Now you can access the app configuration at [your dashboard]/apps and click on [klavio]
|
6. Now you can access the app configuration at [your dashboard]/apps and click on [klaviyo]
|
||||||
7. Read how to connect the app with the Klaviyo [here](https://github.com/saleor/saleor-app-template)
|
7. Read how to connect the app with the Klaviyo [here](https://github.com/saleor/saleor-app-template)
|
||||||
|
|
||||||
### Local development without CLI
|
### Local development without CLI
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# How to connect your App with Klavio
|
# How to connect your App with Klaviyo
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ First and foremost, you need to perform an initial API call to Klaviyo, which wi
|
||||||
|
|
||||||
Let's navigate to "Customers" and create the first, dummy customer.
|
Let's navigate to "Customers" and create the first, dummy customer.
|
||||||
|
|
||||||
Then, open Klavio [Metrics page](https://www.klaviyo.com/analytics/metrics).
|
Then, open Klaviyo [Metrics page](https://www.klaviyo.com/analytics/metrics).
|
||||||
|
|
||||||
Your Metric should be visible on the list:
|
Your Metric should be visible on the list:
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "saleor-app-klaviyo",
|
"name": "saleor-app-klaviyo",
|
||||||
"version": "1.8.2",
|
"version": "1.9.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm generate && next build",
|
"build": "pnpm generate && next build",
|
||||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||||
|
@ -14,12 +14,12 @@
|
||||||
"@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.41.1",
|
"@saleor/app-sdk": "0.43.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.106",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||||
|
"@sentry/nextjs": "7.67.0",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"clsx": "^1.2.1",
|
|
||||||
"graphql": "16.7.1",
|
"graphql": "16.7.1",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"next": "13.4.8",
|
"next": "13.4.8",
|
||||||
|
@ -31,11 +31,11 @@
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"usehooks-ts": "^2.9.1",
|
"usehooks-ts": "^2.9.1",
|
||||||
"vite": "4.3.9",
|
"vite": "4.4.8",
|
||||||
"vitest": "0.31.3"
|
"vitest": "0.34.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "4.0.1",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/introspection": "4.0.0",
|
"@graphql-codegen/introspection": "4.0.0",
|
||||||
"@graphql-codegen/schema-ast": "4.0.0",
|
"@graphql-codegen/schema-ast": "4.0.0",
|
||||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
"clean-publish": "^4.0.1",
|
"clean-publish": "^4.0.1",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.46.0",
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
import { useTheme } from "@saleor/macaw-ui/next";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
// todo move to shared
|
|
||||||
export function ThemeSynchronizer() {
|
|
||||||
const { appBridgeState } = useAppBridge();
|
|
||||||
const { setTheme } = useTheme();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!setTheme || !appBridgeState?.theme) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appBridgeState.theme === "light") {
|
|
||||||
setTheme("defaultLight");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appBridgeState.theme === "dark") {
|
|
||||||
setTheme("defaultDark");
|
|
||||||
}
|
|
||||||
}, [appBridgeState?.theme, setTheme]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { Box } from "@saleor/macaw-ui/next";
|
|
||||||
import { PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
export function AppColumnsLayout({ children }: PropsWithChildren<{}>) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
display={"grid"}
|
|
||||||
__gridTemplateColumns={"280px auto 280px"}
|
|
||||||
gap={4}
|
|
||||||
__maxWidth={"1180px"}
|
|
||||||
marginX={"auto"}
|
|
||||||
marginY={0}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,11 +1,8 @@
|
||||||
import "@saleor/macaw-ui/next/style";
|
|
||||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||||
import React from "react";
|
import { NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
|
||||||
import { AppProps } from "next/app";
|
|
||||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
|
||||||
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
|
||||||
import { NoSSRWrapper } from "@saleor/apps-shared";
|
import "@saleor/macaw-ui/next/style";
|
||||||
import { ThemeSynchronizer } from "../hooks/theme-synchronizer";
|
import { AppProps } from "next/app";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure instance is a singleton.
|
* Ensure instance is a singleton.
|
||||||
|
@ -18,7 +15,9 @@ function SaleorApp({ Component, pageProps }: AppProps) {
|
||||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ThemeSynchronizer />
|
<ThemeSynchronizer />
|
||||||
<Component {...pageProps} />
|
<Box padding={10}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AppBridgeProvider>
|
</AppBridgeProvider>
|
||||||
</NoSSRWrapper>
|
</NoSSRWrapper>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue