Compare commits

...

50 commits

Author SHA1 Message Date
4b12982597 add changeset 2023-09-30 22:22:32 +02:00
292a5bdb0c emails-and-messages redis apl support 2023-09-30 22:03:02 +02:00
Lukasz Ostrowski
653b98df86
Release apps (#1041)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-28 16:02:35 +02:00
Krzysztof Wolski
ae6dbb125b
Remove cache update on product webhooks (#1036)
* Product feed - change max function exec time to 5 minutes

* Remove cache update on product webhooks

* Add webhooks migration

---------

Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-27 12:56:12 +02:00
Lukasz Ostrowski
6948fe41ca
Update CODEOWNERS 2023-09-23 10:49:49 +02:00
Lukasz Ostrowski
2a22cf47f2
🚀 Release apps (#988)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-18 10:20:01 +02:00
Przemysław Łada
927d2b3bb6
Use full commit hash when referencing GitHub Action (#1002)
* Use full commit hash when referencing GitHub Action

* Use full commit hash in changeset-checker
2023-09-18 09:28:24 +02:00
dependabot[bot]
5dee65ad2c
[skip ci]: Bump the codegen group with 1 update (#990)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
2023-09-13 11:04:10 +02:00
Przemysław Łada
c6db32b40e
Fix broken pnpm lock (#1009) 2023-09-08 20:38:48 +02:00
dependabot[bot]
5d3d81d5c2
[skip ci]: Bump @hookform/resolvers from 2.9.11 to 3.3.1 (#993)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-08 15:35:57 +02:00
Przemysław Łada
52c46987f3
Replace assign-pr-creator-action with GitHub CLI (#1008) 2023-09-08 15:35:32 +02:00
Lukasz Ostrowski
1982d81f17
Extend pricing data in Algolia (#998)
Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
2023-09-08 14:05:37 +02:00
Krzysztof Wolski
767d0be722
Add mandatory check for the changeset (#1000)
* Add mandatory check for the changeset

* Skip check on label

* Fx typo and add synchronize event
2023-09-08 14:04:34 +02:00
dependabot[bot]
2951fb3ab6
[skip ci]: Bump jotai from 2.0.0 to 2.4.2 (#1007)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lukasz Ostrowski <lukasz.ostrowski@saleor.io>
2023-09-08 10:40:34 +00:00
Krzysztof Wolski
7e0755ec9e
Webhook helpers (#949)
* WIP

* Added script and making implementation more roboust

* Added rollback on issues with the migration

* Cleanup the code

* Use allSettled instead of all

* Do not check spelling in schema files.
Schema is pulled from the API and is not controlled by our team

* Update the pkg json

* Fix typo on log message

* Add dedupe to the ignored words.
Its used by codegen

* Add changesets
2023-09-07 13:04:23 +02:00
Adrian Pilarczyk
a32fe7caf4
feat: taxes client logs (#944)
* feat: 🚧 add skeleton logs page

* feat:  add ClientLogsMetadataRepository

* feat:  add AvataxClientLogger and router

* feat:  implement avatax client logs in order confirmed

* feat: 🚧 add skeleton ui for logs

* feat:  add avatax client logger

* refactor: ♻️ move breadcrumbs to page lvl

* feat:  logger per config

* feat:  add logs to remaining events

* refactor: ♻️ pass clientLogger from webhook service

* feat:  add taxjar logger

* test:  add tests for logs push

* feat:  add getAll test

* feat:  add missing taxjar page

* refactor: ♻️ move unshiftItemToLimitedArray to log-utils and test

* fix: 🐛 label

* refactor: 💄 styles

* build: 👷 changeset

* fix: 🐛 typo

* refactor: ♻️ address feedback on backend side

* refactor: ♻️ adjust UI to merging of avatax & taxjar logs

* feat:  make client logs table box scrollable

* fix: 🐛 size of container

* fix: 🐛 loading on isRefetching, not isFetching

* fix: 🐛 no lines early error
2023-09-07 10:42:54 +02:00
dependabot[bot]
4a2275e999
[skip ci]: Bump the trpc group with 4 updates (#987)
Bumps the trpc group with 4 updates: [@trpc/client](https://github.com/trpc/trpc/tree/HEAD/packages/client), [@trpc/next](https://github.com/trpc/trpc/tree/HEAD/packages/next), [@trpc/react-query](https://github.com/trpc/trpc/tree/HEAD/packages/react) and [@trpc/server](https://github.com/trpc/trpc/tree/HEAD/packages/server).


Updates `@trpc/client` from 10.34.0 to 10.38.1
- [Release notes](https://github.com/trpc/trpc/releases)
- [Commits](https://github.com/trpc/trpc/commits/v10.38.1/packages/client)

Updates `@trpc/next` from 10.34.0 to 10.38.1
- [Release notes](https://github.com/trpc/trpc/releases)
- [Commits](https://github.com/trpc/trpc/commits/v10.38.1/packages/next)

Updates `@trpc/react-query` from 10.34.0 to 10.38.1
- [Release notes](https://github.com/trpc/trpc/releases)
- [Commits](https://github.com/trpc/trpc/commits/v10.38.1/packages/react)

Updates `@trpc/server` from 10.34.0 to 10.38.1
- [Release notes](https://github.com/trpc/trpc/releases)
- [Commits](https://github.com/trpc/trpc/commits/v10.38.1/packages/server)

---
updated-dependencies:
- dependency-name: "@trpc/client"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: trpc
- dependency-name: "@trpc/next"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: trpc
- dependency-name: "@trpc/react-query"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: trpc
- dependency-name: "@trpc/server"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: trpc
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-06 15:24:18 +02:00
dependabot[bot]
a5f226bdef
[skip ci]: Bump the rtl group with 1 update (#991)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-06 14:49:20 +02:00
Lukasz Ostrowski
a0a607ce99
Update app-sdk to 0.43.1 (#956) 2023-09-06 12:26:04 +00:00
Lukasz Ostrowski
6ac7799d72
Update dependabot.yaml 2023-09-06 14:23:31 +02:00
Lukasz Ostrowski
4aee4e11f8
Invoices, Klaviyo: Refactor to shared components (#989) 2023-09-06 12:22:54 +00:00
Lukasz Ostrowski
86bc946b3e
Update README.md (#962) 2023-09-06 14:22:40 +02:00
Lukasz Ostrowski
1033b93747
Update pnpm version expected by the monorepo (#985) 2023-09-06 10:50:07 +02:00
Lukasz Ostrowski
2e296996cb
Update sentry and add Sentry CLI to the workspace (#986) 2023-09-06 10:24:45 +02:00
Lukasz Ostrowski
068e529cfe
Update dependabot.yaml 2023-09-06 09:59:31 +02:00
dependabot[bot]
3bfcfa5b01
[skip ci]: Bump prettier from 3.0.1 to 3.0.3 (#983)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-06 09:47:49 +02:00
Lukasz Ostrowski
6688a55102
Update PULL_REQUEST_TEMPLATE.md 2023-09-05 19:00:46 +02:00
dependabot[bot]
4efebde36d
[skip ci]: Bump eslint-plugin-react from 7.32.2 to 7.33.2 (#979)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 11:47:00 +02:00
Lukasz Ostrowski
9f76a2f1f8
Update dependabot.yaml 2023-09-05 09:47:19 +02:00
Lukasz Ostrowski
627712bef3
Update dependabot.yaml 2023-09-05 09:12:35 +02:00
Lukasz Ostrowski
8e4941fd2a
🚀 Release apps (#942)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-09-05 08:58:31 +02:00
Lukasz Ostrowski
55315b89f3
Fix Search webhooks toggling (#961)
* Fix Search webhooks toggling

* fix test
2023-09-04 20:13:58 +02:00
Lukasz Ostrowski
30140eea06
Improved some spellings and add some words to allow list (#959) 2023-09-04 11:53:44 +02:00
Lukasz Ostrowski
e8660e8bb9
Extract shared packages (#948)
* [skip ci] tRPC shared package

* [skip ci] tRPC shared package - fix

* [skip ci] shared package - app sections

* [skip ci] segment - implement shared components

* [skip ci] extract theme synchronizer

* extract components and implement them in apps

* cms - extract shared packages

* Fix imports

* remove urql from peer deps
2023-09-04 11:30:57 +02:00
Adrian Pilarczyk
ceddcf96eb
refactor: ♻️ omit more in cspell config (#957) 2023-09-04 11:21:28 +02:00
Lukasz Ostrowski
1e3c08c029
Algolia fields filtering (#946)
* wip

* crud for algolia fields settings

* add ui form fields confiugraion

* adjust app to new config

* filter mapping with fields

* fix lang

* fix lang
2023-09-01 17:01:41 +02:00
Krzysztof Wolski
23e71bc7a2
Fix save button label (#950) 2023-09-01 14:08:10 +02:00
Krzysztof Wolski
261e9d1a0c
Product Feed: Add configurable image size and additional photos (#915)
* Add configurable image size and additional photos

* CR fixes

* CSpell fixes
2023-09-01 13:35:02 +02:00
Adrian Pilarczyk
0aa1d12cc4
feat: ⚗️ add check-spelling.yml gh action (#947)
* feat: ⚗️ add check-spelling.yml gh action

* chore: ⚗️ make typos to test gh action

* feat:  add cspell config

* fix: 🐛 typos

* feat: ⚗️ test verbose

* Revert "feat: ⚗️ test verbose"

This reverts commit 3bf36f5a29b6af2ca969a92f0bc61e12e89d8a44.

* feat:  add cspell locally

scripts for all files & lint-staged

* feat:  add new words to dictionary in cspell.json

* refactor: 🚚 cspell.json -> cspell.config.json

* Revert "refactor: 🚚 cspell.json -> cspell.config.json"

This reverts commit b5c96f6909cf5f0ab1173255ef7ded7e360ca83f.

* Revert "feat:  add new words to dictionary in cspell.json"

This reverts commit 69d8bdd3f9ed19ab07044e3e986c8d0b06114e43.

* feat:  add ignorePaths

* feat: add more ignore

* feat:  add words to cspell.json

* refactor: ♻️ change dictionary in cspell.json

* refactor: make words capitalized

* fix: 🐛 add md/mdx to check-spelling
2023-09-01 11:44:20 +02:00
Lukasz Ostrowski
ed30a818e3
Refactors: webhooks, config (#941) 2023-08-30 12:17:44 +02:00
Lukasz Ostrowski
056209486c
Release apps (#934)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-30 10:43:22 +02:00
Lukasz Ostrowski
0f84985c98
Add tRPC to search app (#940)
* Add tRPC to search app

* Implemented configuration endpoint in trpc

* replace settings manager to use shared factory

* replace configuration calls to trpc
2023-08-29 22:53:51 +02:00
Krzysztof Wolski
2a1385bab1
Search: Fix stale variants after product removal, add indices setup and product descriptions (#932)
* Move plaintext renderer to shared package and use it for Algolia documents

* Fix stale variants after product removal, add indices setup

* Search: webhooks migration script (#936)

* Add webhook recreation script

* Add changeset
2023-08-29 13:19:31 +02:00
Lukasz Ostrowski
a81f061fcf
update macaw (#925) 2023-08-29 10:47:50 +02:00
Lukasz Ostrowski
d9d0f64a01
Fix CMS modal bg (#935) 2023-08-28 21:51:15 +02:00
Krzysztof Wolski
fcc37e7c7e
Remove clsx package which is no longer used (#899)
* Remove clsx package which is no longer used

* Add changeset

* Remove clsx from data importer

* Remove unused clsx package
2023-08-28 19:23:11 +02:00
Adrian Pilarczyk
254cd4c9a3
Fix typo in Segment (#933)
* Fix typo

* build: 👷 add changeset
2023-08-28 13:41:54 +02:00
Lukasz Ostrowski
17925f2115
Release apps (#917)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-28 08:19:32 +02:00
Lukasz Ostrowski
e9378e7af7
fix payloadcms fields (#924)
Co-authored-by: Adrian Pilarczyk <admin@peelar.dev>
2023-08-25 15:33:18 +02:00
Adrian Pilarczyk
34efd39dcf
fix customer code calculate taxes (#922)
* fix: 🐛 calculate taxes customerCode

* build: 👷 changeset

* refactor: ♻️ address feedback

* feat:  add migration

* Empty-Commit
2023-08-25 14:50:47 +02:00
331 changed files with 79916 additions and 4170 deletions

View file

@ -1,5 +0,0 @@
---
"saleor-app-cms-v2": patch
---
Fix styling of modal in the dark mode

View file

@ -1,5 +0,0 @@
---
"saleor-app-taxes": minor
---
Removed all the code related to the deprecated OrderCreated & OrderFulfilled flow. The migration process began in version 1.13.0. All the cloud environments had been migrated automatically.

View 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

View file

@ -1,5 +0,0 @@
---
"saleor-app-cms-v2": minor
---
Added Payload CMS support.

View file

@ -1,5 +0,0 @@
---
"saleor-app-taxes": patch
---
Fixed the error when checkout couldn't calculate taxes when no customerCode was provided. In calculate taxes, the customerCode is now derived from issuingPrincipal's id.

View file

@ -8,5 +8,4 @@
## Checklist
- [ ] `.github/dependabot.yaml` is up-to date.
- [ ] I added changesets and [read good practices](/.changeset/README.md).

View file

@ -2,118 +2,18 @@ version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
open-pull-requests-limit: 1
schedule:
interval: "daily"
commit-message:
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
- package-ecosystem: "npm"
open-pull-requests-limit: 1
directory: apps/segment
commit-message:
prefix: "[skip ci]"
schedule:
interval: weekly

View file

@ -6,9 +6,12 @@ on:
jobs:
assign_creator:
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
steps:
- name: Assign PR to creator
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
env:
GITHUB_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
View 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

View file

@ -17,7 +17,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: JamieMason/syncpack-github-action@0.2.2
- uses: JamieMason/syncpack-github-action@c145cec44b3731b3fe8e859679e240d6ae011f0f
continue-on-error: true
with:
package-manager: "pnpm"

10
.github/workflows/check-spelling.yml vendored Normal file
View 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

View file

@ -25,11 +25,11 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
- uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598
name: Install pnpm
- run: pnpm install
- name: Create Release Pull Request
uses: changesets/action@v1
uses: changesets/action@f13b1baaa620fde937751f5d2c3572b9da32af23
id: changesets
with:
title: 🚀 Release apps

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
- uses: actions/setup-node@v3
with:
node-version: 18

3
.gitignore vendored
View file

@ -42,4 +42,5 @@ apps/**/generated
.sentryclirc
.vscode/
.vscode/
.cspellcache

View file

@ -1 +1 @@
* @saleor/appstore
* @saleor/delivery-engineering-js

View file

@ -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.
- [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
You can find the documentation for saleor/apps on [docs.saleor.io](https://docs.saleor.io/docs/3.x/developer/app-store/development).

View file

@ -1,5 +1,54 @@
# 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

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-cms-v2",
"version": "2.2.2",
"version": "2.3.3",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -13,18 +13,18 @@
},
"dependencies": {
"@datocms/cma-client-browser": "2.0.0",
"@hookform/resolvers": "^3.1.0",
"@saleor/app-sdk": "0.41.1",
"@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.118",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*",
"@sentry/nextjs": "7.55.2",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "^4.29.19",
"@trpc/client": "10.34.0",
"@trpc/next": "10.34.0",
"@trpc/react-query": "10.34.0",
"@trpc/server": "10.34.0",
"@trpc/client": "10.38.1",
"@trpc/next": "10.38.1",
"@trpc/react-query": "10.38.1",
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"contentful-management": "10.38.3",
@ -48,14 +48,14 @@
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3",
"@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",
"@types/qs": "^6.9.7",
"@types/react": "18.2.5",

View file

@ -6,26 +6,20 @@ import { Select } from "@saleor/react-hook-form-macaw";
import { useRouter } from "next/router";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { ButtonsBox } from "../ui/buttons-box";
import { ProvidersResolver } from "../providers/providers-resolver";
import { Skeleton } from "../ui/skeleton";
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
const FormSchema = z.object({
connectionId: z.string().min(7),
});
const EmptyState = () => (
<Box
display="flex"
paddingY={4}
flexDirection={"column"}
gap={4}
alignItems={"center"}
justifyContent={"center"}
>
<Text variant="heading">No connections configured</Text>
<Text>Create a channel connection above to enable bulk synchronization.</Text>
</Box>
<Layout.AppSectionCard>
<Box display="flex" flexDirection={"column"} gap={4} justifyContent={"center"}>
<Text variant="heading">Bulk products synchronization</Text>
<Text>Create a channel connection above to enable bulk synchronization.</Text>
</Box>
</Layout.AppSectionCard>
);
export const BulkSyncSection = () => {
@ -42,7 +36,7 @@ export const BulkSyncSection = () => {
});
if (!connections || !providers) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
if (connections.length === 0) {
@ -50,7 +44,7 @@ export const BulkSyncSection = () => {
}
return (
<Box>
<Layout.AppSectionCard>
<Text as="h2" marginBottom={6} variant="heading">
Bulk products synchronization
</Text>
@ -88,6 +82,6 @@ export const BulkSyncSection = () => {
<Button type="submit">Start sync</Button>
</ButtonsBox>
</Box>
</Box>
</Layout.AppSectionCard>
);
};

View file

@ -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 { useEffect, useRef, useState } from "react";
import { ChannelProviderConnectionConfig, ProvidersConfig } from "../configuration";
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 { useFetchAllProducts } from "./use-fetch-all-products";
@ -14,16 +12,19 @@ import { useDashboardNotification } from "@saleor/apps-shared";
const FetchProductsStep = (props: { onButtonClick(): void }) => {
return (
<Box>
<Layout.AppSectionCard
footer={
<ButtonsBox>
<Button onClick={props.onButtonClick}>Prefetch products</Button>
</ButtonsBox>
}
>
<Text variant="heading" as="h2" marginBottom={4}>
Saleor products fetch
</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>
<ButtonsBox>
<Button onClick={props.onButtonClick}>Prefetch products</Button>
</ButtonsBox>
</Box>
</Layout.AppSectionCard>
);
};
@ -66,7 +67,7 @@ export const BulkSyncView = ({
const { products, finished: saleorProductsFetchFinished } = useFetchAllProducts(
state === "fetching",
connection.channelSlug
connection.channelSlug,
);
const { productsStatusList, setInitialProducts, setItemStatus, finished } =
@ -121,9 +122,14 @@ export const BulkSyncView = ({
]}
/>
<AppSection
<Layout.AppSection
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) {
case "initial": {
return (
@ -149,33 +155,29 @@ export const BulkSyncView = ({
}
}
})()}
heading="1. Fetch products"
sideContent={
<Text>First pre-fetch all Product Variants from Saleor. Do not close the app.</Text>
}
/>
</Layout.AppSection>
{(state === "fetched" || state === "uploading") && productsStatusList && (
<AppSection
<Layout.AppSection
marginTop={14}
heading="2. Upload to the CMS"
sideContent={<Text>Send listed variants to the CMS</Text>}
mainContent={
<Box>
<Text as="h2" marginBottom={4} variant="heading">
Upload products
</Text>
{state === "fetched" && (
<Box marginBottom={4}>
<Text as="p" marginBottom={2}>
Verify products below and click the button to start uploading.
</Text>
<Button onClick={() => setState("uploading")}>Start uploading</Button>
</Box>
)}
<VariantsSyncStatusList marginTop={8} variants={productsStatusList} />
</Box>
}
/>
>
<Layout.AppSectionCard>
<Text as="h2" marginBottom={4} variant="heading">
Upload products
</Text>
{state === "fetched" && (
<Box marginBottom={4}>
<Text as="p" marginBottom={2}>
Verify products below and click the button to start uploading.
</Text>
<Button onClick={() => setState("uploading")}>Start uploading</Button>
</Box>
)}
<VariantsSyncStatusList marginTop={8} variants={productsStatusList} />
</Layout.AppSectionCard>
</Layout.AppSection>
)}
</Box>
);

View file

@ -1,5 +1,4 @@
import { Button, Text } from "@saleor/macaw-ui/next";
import { ButtonsBox } from "../ui/buttons-box";
import { Modal } from "../ui/modal";
import {
AddConnectionForm,
@ -7,7 +6,7 @@ import {
AddConnectionFormSchema,
} from "./add-connection-form";
import { trpcClient } from "../trpc/trpc-client";
import { Skeleton } from "../ui/skeleton";
import { ButtonsBox, SkeletonLayout } from "@saleor/apps-ui";
const defaultValues: AddConnectionFormSchema = { channelSlug: "", providerId: "" };
@ -15,7 +14,7 @@ export const AddConnectionModal = (props: { onSuccess(): void; onClose(): void }
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
if (!providers) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
const { mutateAsync: addProviderMutate, isLoading } =

View file

@ -1,35 +1,31 @@
import { useDashboardNotification } from "@saleor/apps-shared";
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
import { Box, Button, Text } from "@saleor/macaw-ui/next";
import { useState } from "react";
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 { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
import { ConnectionsList } from "./connections-list";
import { Skeleton } from "../ui/skeleton";
const NoConnections = (props: { onCreate(): void; enabled: boolean }) => (
<Box>
<ChanelProviderConnectionsSectionHeader />
<Text marginBottom={4} as="p">
<Text as="p">
No channels connected yet.{" "}
{!props.enabled &&
"Ensure you have created a provider configuration that can be connected first."}
</Text>
{props.enabled && (
<ButtonsBox>
<Button onClick={props.onCreate}>Create first connection</Button>
</ButtonsBox>
)}
</Box>
);
export const ChannelProviderConnectionList = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const { data: connectionsData, refetch: refetchConnections } =
trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
const {
data: connectionsData,
refetch: refetchConnections,
isLoading,
} = trpcClient.channelsProvidersConnection.fetchConnections.useQuery();
const { mutate: removeConnection } =
trpcClient.channelsProvidersConnection.removeConnection.useMutation({
@ -45,19 +41,33 @@ export const ChannelProviderConnectionList = () => {
const { data: providers } = trpcClient.providersConfigs.getAll.useQuery();
if (!providers) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
const handleDelete = (connectionId: string) => {
removeConnection({ id: connectionId });
};
if (!connectionsData) {
return <Text>Loading</Text>;
if (isLoading || !connectionsData) {
return <SkeletonLayout.Section />;
}
return (
<Box>
<Layout.AppSectionCard
footer={
providers.length > 0 && (
<ButtonsBox>
<Button
onClick={() => {
setDialogOpen(true);
}}
>
Add connection
</Button>
</ButtonsBox>
)
}
>
{dialogOpen && (
<AddConnectionModal
onClose={() => {
@ -79,17 +89,6 @@ export const ChannelProviderConnectionList = () => {
/>
)}
{connectionsData.length > 0 && <ConnectionsList onRemove={handleDelete} />}
{connectionsData.length > 0 && (
<ButtonsBox marginTop={6}>
<Button
onClick={() => {
setDialogOpen(true);
}}
>
Add connection
</Button>
</ButtonsBox>
)}
</Box>
</Layout.AppSectionCard>
);
};

View file

@ -4,7 +4,7 @@ import React from "react";
import { trpcClient } from "../trpc/trpc-client";
import { ChanelProviderConnectionsSectionHeader } from "./channel-provider-connections-section-header";
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 }) => {
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();
if (!data || !providers) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
return (

View file

@ -5,8 +5,8 @@ import { ProvidersConfig } from "../configuration";
import { ProvidersResolver } from "../providers/providers-resolver";
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 { push } = useRouter();
@ -47,30 +47,45 @@ export const ProvidersList = () => {
const { push } = useRouter();
if (!data) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
if (data.length === 0) {
return (
<Box>
<Layout.AppSectionCard
footer={
<ButtonsBox>
<Button
onClick={() => {
push("/add-provider");
}}
>
Add first CMS configuration
</Button>
</ButtonsBox>
}
>
<Text as="p" marginBottom={4}>
No configurations yet
</Text>
</Layout.AppSectionCard>
);
}
return (
<Layout.AppSectionCard
footer={
<ButtonsBox>
<Button
onClick={() => {
push("/add-provider");
}}
>
Add first CMS configuration
Add CMS configuration
</Button>
</ButtonsBox>
</Box>
);
}
return (
<Box>
}
>
{data.length && (
<Box>
<Text variant="heading" as="h2" marginBottom={4}>
@ -79,15 +94,6 @@ export const ProvidersList = () => {
<ProvidersTable providers={data} />
</Box>
)}
<ButtonsBox marginTop={8}>
<Button
onClick={() => {
push("/add-provider");
}}
>
Add CMS configuration
</Button>
</ButtonsBox>
</Box>
</Layout.AppSectionCard>
);
};

View file

@ -7,9 +7,7 @@ import { useForm } from "react-hook-form";
import { BuilderIoProviderConfig, SaleorProviderFieldsMappingKeys } from "../../configuration";
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
import { trpcClient } from "../../trpc/trpc-client";
import { ButtonsBox } from "../../ui/buttons-box";
import { TextLink } from "@saleor/apps-ui";
import { Skeleton } from "@/modules/ui/skeleton";
import { ButtonsBox, SkeletonLayout, TextLink } from "@saleor/apps-ui";
type FormShape = Omit<BuilderIoProviderConfig.InputShape, "type">;
const FormSchema = BuilderIoProviderConfig.Schema.Input.omit({ type: true });
@ -127,7 +125,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
size="small"
control={control}
name={`productVariantFieldsMapping.${saleorField}`}
label="Builer.io Field"
label="Builder.io Field"
/>
</Box>
))}
@ -207,11 +205,11 @@ const EditFormVariant = (props: { configId: string }) => {
});
if (!data) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
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 (

View file

@ -9,8 +9,7 @@ import { useDashboardNotification } from "@saleor/apps-shared";
import { ContentfulProviderConfig } from "../../configuration/schemas/contentful-provider.schema";
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
import { zodResolver } from "@hookform/resolvers/zod";
import { ButtonsBox } from "../../ui/buttons-box";
import { TextLink } from "@saleor/apps-ui";
import { ButtonsBox, TextLink } from "@saleor/apps-ui";
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
type FormSchema = Omit<ContentfulProviderConfig.InputShape, "type">;

View file

@ -8,8 +8,8 @@ import React, { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
import { trpcClient } from "../../trpc/trpc-client";
import { ButtonsBox } from "../../ui/buttons-box";
import { DatocmsProviderConfig } from "@/modules/configuration/schemas/datocms-provider.schema";
import { ButtonsBox } from "@saleor/apps-ui";
type FormShape = Omit<DatocmsProviderConfig.InputShape, "type">;

View file

@ -9,8 +9,7 @@ import React from "react";
import { useForm } from "react-hook-form";
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
import { trpcClient } from "../../trpc/trpc-client";
import { ButtonsBox } from "../../ui/buttons-box";
import { TextLink } from "@saleor/apps-ui";
import { ButtonsBox, TextLink } from "@saleor/apps-ui";
type FormShape = Omit<PayloadCmsProviderConfig.InputShape, "type">;
@ -82,17 +81,19 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
>
Read more in Payload docs
</TextLink>
<Text as="p" marginBottom={4}>
<Text as="p" marginBottom={6}>
If your API is open (e.g. for development purposes) leave both fields empty.
</Text>
<Input
control={control}
name="authenticatedUserSlug"
label="Authenticated user slug"
placeholder="e.g. apps"
/>
<Input control={control} name="authToken" type="password" label="User API Key" />
<Box 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}>

View file

@ -7,8 +7,7 @@ import { useForm } from "react-hook-form";
import { SaleorProviderFieldsMappingKeys, StrapiProviderConfig } from "../../configuration";
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
import { trpcClient } from "../../trpc/trpc-client";
import { ButtonsBox } from "../../ui/buttons-box";
import { Skeleton } from "@/modules/ui/skeleton";
import { ButtonsBox, SkeletonLayout } from "@saleor/apps-ui";
type FormShape = Omit<StrapiProviderConfig.InputShape, "type">;
@ -189,7 +188,7 @@ const EditFormVariant = (props: { configId: string }) => {
});
if (!data) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
if (data.type !== "strapi") {

View file

@ -9,7 +9,7 @@ dialog {
.dialog-overlay {
z-index: 1;
background: rgba(var(--mu-colors-background-plain), 0.8);
background: color-mix(in srgb, var(--mu-colors-background-plain) 80%, transparent);
backdrop-filter: blur(5px);
content: "";
position: fixed;

View file

@ -1,10 +1,10 @@
import * as trpcNext from "@trpc/server/adapters/next";
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
import { inferAsyncReturnType } from "@trpc/server";
import { getBaseUrl } from "@/modules/shared/get-base-url";
import { getAppBaseUrl } from "@saleor/apps-shared";
export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
const baseUrl = getBaseUrl(req.headers);
const baseUrl = getAppBaseUrl(req.headers);
return {
token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,

View file

@ -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>
);
};

View file

@ -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 };

View file

@ -1,7 +1,6 @@
import { CMSProviders } from "@/modules/providers/providers-registry";
import { AppHeader } from "@/modules/ui/app-header";
import { AppSection } from "@/modules/ui/app-section";
import { Breadcrumbs } from "@saleor/apps-ui";
import { Breadcrumbs, Layout } from "@saleor/apps-ui";
import { Box, Button, Text } from "@saleor/macaw-ui/next";
import { NextPage } from "next";
import { useRouter } from "next/router";
@ -16,14 +15,15 @@ const AddProviderPage: NextPage = () => {
text="Connect CMS platforms to the App."
breadcrumbs={[<Breadcrumbs.Item key="provider">Add Provider</Breadcrumbs.Item>]}
/>
<AppSection
<Layout.AppSection
heading="Select CMS provider"
sideContent={
<Box>
<Text>App allows to connect one or more CMS platforms. You can add more later.</Text>
</Box>
}
mainContent={
>
<Layout.AppSectionCard>
<Box
display="grid"
__gridTemplateColumns="auto auto auto"
@ -54,8 +54,8 @@ const AddProviderPage: NextPage = () => {
</React.Fragment>
))}
</Box>
}
/>
</Layout.AppSectionCard>
</Layout.AppSection>
</Box>
);
};

View file

@ -2,8 +2,7 @@ import { CMSType } from "@/modules/providers/providers-registry";
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
import { AppHeader } from "@/modules/ui/app-header";
import { AppSection } from "@/modules/ui/app-section";
import { Breadcrumbs } from "@saleor/apps-ui";
import { Breadcrumbs, Layout } from "@saleor/apps-ui";
import { Box, Text } from "@saleor/macaw-ui/next";
import { NextPage } from "next";
import { useRouter } from "next/router";
@ -32,7 +31,7 @@ const AddProviderPage: NextPage = () => {
]}
/>
<AppSection
<Layout.AppSection
heading={`Set up ${provider.displayName}`}
sideContent={
<Box>
@ -40,8 +39,11 @@ const AddProviderPage: NextPage = () => {
{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}
</Box>
}
mainContent={<FormComponent />}
/>
>
<Layout.AppSectionCard>
<FormComponent />
</Layout.AppSectionCard>
</Layout.AppSection>
</Box>
);
};

View file

@ -1,10 +1,9 @@
import { BulkSyncView } from "@/modules/bulk-sync/bulk-sync-view";
import { trpcClient } from "@/modules/trpc/trpc-client";
import { SkeletonLayout } from "@saleor/apps-ui";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { z } from "zod";
import { Text } from "@saleor/macaw-ui/next";
import { Skeleton } from "@/modules/ui/skeleton";
const BulkSyncPage: NextPage = () => {
const { query } = useRouter();
@ -23,7 +22,7 @@ const BulkSyncPage: NextPage = () => {
},
{
enabled: !!parsedID,
}
},
);
const {
@ -36,7 +35,7 @@ const BulkSyncPage: NextPage = () => {
},
{
enabled: !!connection,
}
},
);
if ((providerFetched && !provider) || (connectionFetched && !connection)) {
@ -45,7 +44,7 @@ const BulkSyncPage: NextPage = () => {
}
if (connectionLoading || providerLoading) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
if (!(provider && connection)) {

View file

@ -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 { ProvidersList } from "@/modules/providers-listing/providers-list";
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 { NextPage } from "next";
@ -10,7 +10,7 @@ const ConfigurationPage: NextPage = () => {
return (
<Box>
<AppHeader />
<AppSection
<Layout.AppSection
marginBottom={14}
heading="Providers configuration"
sideContent={
@ -18,9 +18,10 @@ const ConfigurationPage: NextPage = () => {
<Text>Configure one or more CMS providers to synchronize Saleor products.</Text>
</Box>
}
mainContent={<ProvidersList />}
/>
<AppSection
>
<ProvidersList />
</Layout.AppSection>
<Layout.AppSection
marginBottom={14}
heading="Automatic synchronization"
sideContent={
@ -31,9 +32,10 @@ const ConfigurationPage: NextPage = () => {
</Text>
</Box>
}
mainContent={<ChannelProviderConnectionList />}
/>
<AppSection
>
<ChannelProviderConnectionList />
</Layout.AppSection>
<Layout.AppSection
heading="Initial sync"
sideContent={
<Box>
@ -44,8 +46,9 @@ const ConfigurationPage: NextPage = () => {
<Text as="p">Its recommended to run this flow initially, once app is configured.</Text>
</Box>
}
mainContent={<BulkSyncSection />}
/>
>
<BulkSyncSection />
</Layout.AppSection>
</Box>
);
};

View file

@ -2,9 +2,7 @@ import { ProvidersResolver } from "@/modules/providers/providers-resolver";
import { trpcClient } from "@/modules/trpc/trpc-client";
import { AppHeader } from "@/modules/ui/app-header";
import { AppSection } from "@/modules/ui/app-section";
import { Skeleton } from "@/modules/ui/skeleton";
import { Breadcrumbs } from "@saleor/apps-ui";
import { Breadcrumbs, Layout, SkeletonLayout } from "@saleor/apps-ui";
import { Box, Text } from "@saleor/macaw-ui/next";
import { NextPage } from "next";
import { useRouter } from "next/router";
@ -20,7 +18,7 @@ const EditProviderPage: NextPage = () => {
},
{
enabled: !!configId,
}
},
);
const provider = useMemo(() => {
@ -28,7 +26,7 @@ const EditProviderPage: NextPage = () => {
}, [data]);
if (isLoading) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
if (isFetched && !data) {
@ -38,7 +36,7 @@ const EditProviderPage: NextPage = () => {
}
if (!provider) {
return <Skeleton.Section />;
return <SkeletonLayout.Section />;
}
const EditForm = ProvidersResolver.getEditProviderFormComponent(provider.type);
@ -53,13 +51,16 @@ const EditProviderPage: NextPage = () => {
<Breadcrumbs.Item key="configname">{data?.configName}</Breadcrumbs.Item>,
]}
/>
<AppSection
<Layout.AppSection
heading="Edit CMS configuration"
mainContent={<EditForm configId={configId} />}
sideContent={
<Box>{provider.formSideInfo && <Box marginTop={6}>{provider.formSideInfo}</Box>}</Box>
}
/>
>
<Layout.AppSectionCard>
<EditForm configId={configId} />
</Layout.AppSectionCard>
</Layout.AppSection>
</Box>
);
};

View file

@ -1,5 +1,34 @@
# 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

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-crm",
"version": "1.7.5",
"version": "1.7.8",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -13,15 +13,15 @@
},
"dependencies": {
"@mailchimp/mailchimp_marketing": "^3.0.80",
"@saleor/app-sdk": "0.41.1",
"@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.118",
"@sentry/nextjs": "7.55.2",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19",
"@trpc/client": "10.34.0",
"@trpc/next": "10.34.0",
"@trpc/react-query": "10.34.0",
"@trpc/server": "10.34.0",
"@trpc/client": "10.38.1",
"@trpc/next": "10.38.1",
"@trpc/react-query": "10.38.1",
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"clsx": "^1.2.1",
@ -42,14 +42,14 @@
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3",
"@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",
"@types/mailchimp__mailchimp_marketing": "^3.0.7",
"@types/react": "18.2.5",

View file

@ -1,5 +1,34 @@
# 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

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-data-importer",
"version": "1.9.4",
"version": "1.9.7",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -15,13 +15,12 @@
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@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/macaw-ui": "0.8.0-pre.118",
"@sentry/nextjs": "7.55.2",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@sentry/nextjs": "7.67.0",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"clsx": "^1.2.1",
"dot-object": "^2.1.4",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
@ -40,7 +39,7 @@
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
@ -48,7 +47,7 @@
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3",
"@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",
"@types/dot-object": "^2.1.2",
"@types/react": "18.2.5",

View file

@ -7,6 +7,12 @@ APL=
REST_APL_ENDPOINT=
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
# Local development variables. When developped locally with Saleor inside docker, these can be set to:
@ -15,4 +21,5 @@ APP_LOG_LEVEL=info
# If developped with tunnels, set this empty, it will fallback to default Next's localhost:3000
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
APP_IFRAME_BASE_URL=
APP_API_BASE_URL=
APP_API_BASE_URL=

View file

@ -1,5 +1,40 @@
# 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

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-emails-and-messages",
"version": "1.9.7",
"version": "1.9.10",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,24 +12,23 @@
"test": "vitest"
},
"dependencies": {
"@hookform/resolvers": "^3.1.0",
"@hookform/resolvers": "^3.3.1",
"@monaco-editor/react": "^4.4.6",
"@saleor/app-sdk": "0.41.1",
"@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.118",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*",
"@sendgrid/client": "^7.7.0",
"@sendgrid/mail": "^7.7.0",
"@sentry/nextjs": "7.55.2",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19",
"@trpc/client": "10.34.0",
"@trpc/next": "10.34.0",
"@trpc/react-query": "10.34.0",
"@trpc/server": "10.34.0",
"@trpc/client": "10.38.1",
"@trpc/next": "10.38.1",
"@trpc/react-query": "10.38.1",
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"clsx": "^1.2.1",
"dotenv": "^16.3.1",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
@ -52,7 +51,7 @@
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
@ -60,7 +59,7 @@
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3",
"@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",
"@types/html-to-text": "^9.0.0",
"@types/mjml": "^4.7.0",

View file

@ -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";
const aplType = process.env.APL ?? "file";
@ -6,6 +6,12 @@ const aplType = process.env.APL ?? "file";
export let apl: APL;
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":
apl = new UpstashAPL();

View file

@ -1,5 +1,7 @@
{
"extends": ["//"],
"extends": [
"//"
],
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
@ -21,7 +23,8 @@
"NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_ENVIRONMENT",
"APP_IFRAME_BASE_URL",
"APP_API_BASE_URL"
"APP_API_BASE_URL",
"REDIS_URL"
]
}
}

View file

@ -1,5 +1,40 @@
# 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

View file

@ -1,13 +1,18 @@
const { withSentryConfig } = require("@sentry/nextjs");
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} */
const nextConfig = {
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(
@ -23,9 +28,7 @@ const configWithSentry = withSentryConfig(
tunnelRoute: "/monitoring",
hideSourceMaps: true,
disableLogger: true,
}
},
);
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-invoices",
"version": "1.15.5",
"version": "1.16.0",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -12,19 +12,20 @@
"test": "vitest"
},
"dependencies": {
"@hookform/resolvers": "^3.1.0",
"@saleor/app-sdk": "0.41.1",
"@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.118",
"@sentry/nextjs": "7.55.2",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/trpc": "workspace:*",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19",
"@trpc/client": "10.34.0",
"@trpc/next": "10.34.0",
"@trpc/react-query": "10.34.0",
"@trpc/server": "10.34.0",
"@trpc/client": "10.38.1",
"@trpc/next": "10.38.1",
"@trpc/react-query": "10.38.1",
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@web-std/file": "^3.0.2",
"clsx": "^1.2.1",
"eslint": "8.46.0",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
@ -41,7 +42,7 @@
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -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;
}

View file

@ -53,6 +53,8 @@ describe("appConfigurationRouter", function () {
token: "TOKEN",
saleorApiUrl: "http://localhost:8000/graphql/",
appId: "app",
ssr: true,
baseUrl: "localhost:3000",
})
.upsertChannelOverride({
channelSlug: "test",

View file

@ -1,13 +1,14 @@
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 { z } from "zod";
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 { 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";
type Props = {
@ -57,12 +58,29 @@ export const AddressForm = (props: Props & InnerFormProps) => {
});
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) => {
return props.onSubmit(data);
})}
>
<Box display={"grid"} gap={3} marginBottom={9}>
<Box display={"grid"} gap={3}>
{fieldsBlock1.map((fieldName) => (
<Controller
key={fieldName}
@ -128,21 +146,7 @@ export const AddressForm = (props: Props & InnerFormProps) => {
/>
))}
</Box>
<Box display={"grid"} justifyContent={"flex-end"} gap={1.5} gridAutoFlow={"column"}>
<Button
variant="tertiary"
onClick={(e) => {
e.stopPropagation();
props.onCancel();
}}
>
<Text color={"textNeutralSubdued"}>Cancel</Text>
</Button>
<Button type="submit" variant="primary">
Save
</Button>
</Box>
</form>
</Layout.AppSectionCard>
);
};
@ -164,9 +168,6 @@ export const ConnectedAddressForm = (props: Props) => {
const { push } = useRouter();
const addressData =
channelOverrideConfigQuery.data && channelOverrideConfigQuery.data[props.channelSlug];
const submitHandler = useCallback(
async (data: AddressV2Shape) => {
return upsertConfigMutation.mutate({
@ -174,7 +175,7 @@ export const ConnectedAddressForm = (props: Props) => {
channelSlug: props.channelSlug,
});
},
[props.channelSlug, upsertConfigMutation]
[props.channelSlug, upsertConfigMutation],
);
const onCancelHandler = useCallback(() => {
@ -182,7 +183,7 @@ export const ConnectedAddressForm = (props: Props) => {
}, [push]);
if (channelOverrideConfigQuery.isLoading) {
return <Text color={"textNeutralSubdued"}>Loading</Text>;
return <SkeletonLayout.Section />;
}
return (

View file

@ -1,29 +1,25 @@
import { Box, Text } from "@saleor/macaw-ui/next";
import { DefaultShopAddress } from "../../shop-info/ui/default-shop-address";
import { AppSection } from "../../ui/AppSection";
import { PerChannelConfigList } from "../../channels/ui/per-channel-config-list";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
import { Layout } from "@saleor/apps-ui";
export const AppConfigView = () => {
const { appBridge } = useAppBridge();
return (
<Box>
<Box
display={"grid"}
justifyContent={"space-between"}
__gridTemplateColumns={"400px 400px"}
gap={10}
__marginBottom={"200px"}
>
<Box>
<Text as={"h1"} variant={"hero"} marginBottom={5}>
Configuration
</Text>
<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={"h1"} variant={"hero"} marginBottom={5}>
Configuration
</Text>
<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>
<Layout.AppSection
marginTop={10}
heading={"Default address of the shop"}
sideContent={
<Text as={"p"} marginBottom={1.5}>
By default it will use{" "}
<a
@ -32,7 +28,7 @@ export const AppConfigView = () => {
appBridge?.dispatch(
actions.Redirect({
to: "/site-settings",
})
}),
);
}}
>
@ -40,22 +36,25 @@ export const AppConfigView = () => {
</a>{" "}
address, but each channel can be configured separately
</Text>
</Box>
<Box>
<DefaultShopAddress />
</Box>
</Box>
<AppSection
includePadding={true}
}
>
<DefaultShopAddress />
</Layout.AppSection>
<Layout.AppSection
marginTop={10}
heading={"Shop address per channel"}
mainContent={<PerChannelConfigList />}
sideContent={
<Text>
Configure custom billing address for each channel. If not set, default shop address will
be used
</Text>
}
/>
>
<Layout.AppSectionCard>
<PerChannelConfigList />
</Layout.AppSectionCard>
</Layout.AppSection>
</Box>
);
};

View file

@ -1,9 +1,9 @@
import { Box, ChevronRightIcon, Text, Button } from "@saleor/macaw-ui/next";
import { AppSection } from "../../ui/AppSection";
import { useRouter } from "next/router";
import { ConnectedAddressForm } from "../ui/address-form";
import { trpcClient } from "../../trpc/trpc-client";
import { useDashboardNotification } from "@saleor/apps-shared";
import { Layout } from "@saleor/apps-ui";
export const ChannelConfigView = () => {
const {
@ -15,7 +15,7 @@ export const ChannelConfigView = () => {
const { notifySuccess } = useDashboardNotification();
if (!channel) {
return null;
return null; // TODO: error
}
return (
@ -29,10 +29,9 @@ export const ChannelConfigView = () => {
<Text>{channel}</Text>
</Box>
</Box>
<AppSection
<Layout.AppSection
includePadding={true}
heading={"Shop address per channel"}
mainContent={<ConnectedAddressForm channelSlug={channel as string} />}
sideContent={
<Box>
<Text marginBottom={5} as={"p"}>
@ -51,7 +50,9 @@ export const ChannelConfigView = () => {
</Button>
</Box>
}
/>
>
<ConnectedAddressForm channelSlug={channel as string} />
</Layout.AppSection>
</Box>
);
};

View file

@ -1,6 +1,7 @@
import { Box, Text, Chip, Button } from "@saleor/macaw-ui/next";
import { trpcClient } from "../../trpc/trpc-client";
import { useRouter } from "next/router";
import { SkeletonLayout } from "@saleor/apps-ui";
const defaultAddressChip = (
<Chip __display={"inline-block"} size={"large"}>
@ -17,7 +18,7 @@ export const PerChannelConfigList = () => {
const { push } = useRouter();
if (shopChannelsQuery.isLoading || channelsOverridesQuery.isLoading) {
return <Text color={"textNeutralSubdued"}>Loading...</Text>;
return <SkeletonLayout.Section />;
}
const renderChannelAddress = (slug: string) => {

View file

@ -2,30 +2,31 @@ import { Box, Text, Button } from "@saleor/macaw-ui/next";
import { trpcClient } from "../../trpc/trpc-client";
import { PropsWithChildren } from "react";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
const Wrapper = ({ children }: PropsWithChildren<{}>) => {
const { appBridge } = useAppBridge();
return (
<Box>
<Box display={"flex"} justifyContent={"space-between"} marginBottom={5}>
<Text variant={"heading"}>Default address of the shop</Text>
<Button
size={"small"}
variant={"tertiary"}
onClick={() => {
appBridge?.dispatch(
actions.Redirect({
to: "/site-settings",
})
);
}}
>
<Text color={"textNeutralSubdued"}>Edit</Text>
</Button>
</Box>
<Layout.AppSectionCard
footer={
<ButtonsBox>
<Button
onClick={() => {
appBridge?.dispatch(
actions.Redirect({
to: "/site-settings",
}),
);
}}
>
Edit
</Button>
</ButtonsBox>
}
>
<Box>{children}</Box>
</Box>
</Layout.AppSectionCard>
);
};
@ -46,7 +47,7 @@ export const DefaultShopAddress = () => {
if (isLoading) {
return (
<Wrapper>
<Text color={"textNeutralSubdued"}>Loading...</Text>
<SkeletonLayout.Section />
</Wrapper>
);
}
@ -70,6 +71,9 @@ export const DefaultShopAddress = () => {
if (data && data.companyAddress) {
return (
<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"}>
{data.companyAddress.companyName}
</Text>

View file

@ -1,34 +1,13 @@
import { httpBatchLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
import { createHttpBatchLink } from "@saleor/trpc";
import { appBridgeInstance } from "../../pages/_app";
import { AppRouter } from "./trpc-app-router";
function getBaseUrl() {
if (typeof window !== "undefined") return "";
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export const trpcClient = createTRPCNext<AppRouter>({
config({ ctx }) {
config() {
return {
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
headers() {
return {
/**
* Attach headers from app to client requests, so tRPC can add them to context
*/
[SALEOR_AUTHORIZATION_BEARER_HEADER]: appBridgeInstance?.getState().token,
[SALEOR_API_URL_HEADER]: appBridgeInstance?.getState().saleorApiUrl,
};
},
}),
],
links: [createHttpBatchLink(appBridgeInstance)],
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
};
},

View file

@ -1,6 +1,7 @@
import { initTRPC } from "@trpc/server";
import { TrpcContext } from "./trpc-context";
import { Permission } from "@saleor/app-sdk/types";
import { TrpcContext } from "@saleor/trpc";
interface Meta {
requiredClientPermissions?: Permission[];

View file

@ -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>
);
};

View file

@ -6,12 +6,11 @@ import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
import React, { ReactElement } from "react";
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 { Box, ThemeProvider } from "@saleor/macaw-ui/next";
import { NextPage } from "next";
import { ThemeSynchronizer } from "../lib/theme-synchronizer";
/**
* Ensure instance is a singleton.

View file

@ -1,7 +1,7 @@
import * as trpcNext from "@trpc/server/adapters/next";
import { createTrpcContext } from "../../../modules/trpc/trpc-context";
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" });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -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

View file

@ -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 {
cursor: pointer;
text-decoration: none;

View file

@ -1,5 +1,40 @@
# 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

View file

@ -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`
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`
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)
### Local development without CLI

View file

@ -1,4 +1,4 @@
# How to connect your App with Klavio
# How to connect your App with Klaviyo
## 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.
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:

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-klaviyo",
"version": "1.8.4",
"version": "1.9.0",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -14,12 +14,12 @@
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@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/macaw-ui": "0.8.0-pre.118",
"@sentry/nextjs": "7.55.2",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@sentry/nextjs": "7.67.0",
"@urql/exchange-auth": "^2.1.4",
"clsx": "^1.2.1",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
"next": "13.4.8",
@ -35,7 +35,7 @@
"vitest": "0.34.1"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",

View file

@ -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;
}

View file

@ -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>
);
}

View file

@ -1,11 +1,8 @@
import "@saleor/macaw-ui/next/style";
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
import React from "react";
import { AppProps } from "next/app";
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
import { NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
import { NoSSRWrapper } from "@saleor/apps-shared";
import { ThemeSynchronizer } from "../hooks/theme-synchronizer";
import "@saleor/macaw-ui/next/style";
import { AppProps } from "next/app";
/**
* Ensure instance is a singleton.
@ -18,7 +15,9 @@ function SaleorApp({ Component, pageProps }: AppProps) {
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
<ThemeProvider>
<ThemeSynchronizer />
<Component {...pageProps} />
<Box padding={10}>
<Component {...pageProps} />
</Box>
</ThemeProvider>
</AppBridgeProvider>
</NoSSRWrapper>

View file

@ -3,13 +3,8 @@ import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
<Head />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<body>
<Main />
<NextScript />

View file

@ -61,12 +61,20 @@ const handler: NextProtectedApiHandler = async (request, res, ctx) => {
data: await getAppSettings(settings),
});
case "POST": {
await settings.set((request.body as PostRequestBody).data);
try {
await settings.set((JSON.parse(request.body) as PostRequestBody).data);
return res.json({
success: true,
data: await getAppSettings(settings),
});
return res.json({
success: true,
data: await getAppSettings(settings),
});
} catch (e) {
console.error(e);
return res.json({
success: false,
});
}
}
default:
return res.status(405).end();

View file

@ -8,17 +8,17 @@ import { orderCreatedWebhook } from "./webhooks/order-created";
import { orderFullyPaidWebhook } from "./webhooks/order-fully-paid";
const handler = createManifestHandler({
async manifestFactory(context): Promise<AppManifest> {
const { appBaseUrl } = context;
async manifestFactory({ appBaseUrl }): Promise<AppManifest> {
const iframeBaseUrl = process.env.APP_IFRAME_BASE_URL ?? appBaseUrl;
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
return {
about:
"Klaviyo integration allows sending Klaviyo notifications on Saleor events.",
appUrl: appBaseUrl,
about: "Klaviyo integration allows sending Klaviyo notifications on Saleor events.",
appUrl: iframeBaseUrl,
author: "Saleor Commerce",
brand: {
logo: {
default: `${context.appBaseUrl}/logo.png`,
default: `${apiBaseURL}/logo.png`,
},
},
dataPrivacyUrl: "https://saleor.io/legal/privacy/",
@ -27,7 +27,7 @@ const handler = createManifestHandler({
name: "Klaviyo",
permissions: ["MANAGE_USERS", "MANAGE_ORDERS"],
supportUrl: "https://github.com/saleor/apps/discussions",
tokenTargetUrl: `${appBaseUrl}/api/register`,
tokenTargetUrl: `${apiBaseURL}/api/register`,
version: pkg.version,
webhooks: [
customerCreatedWebhook.getWebhookManifest(appBaseUrl),

View file

@ -1,47 +1,31 @@
import { useAppBridge, withAuthorization } from "@saleor/app-sdk/app-bridge";
import { useAppBridge, useAuthenticatedFetch } from "@saleor/app-sdk/app-bridge";
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
import { ChangeEvent, SyntheticEvent, useEffect, useState } from "react";
import { useAppApi } from "../hooks/useAppApi";
import { AppColumnsLayout } from "../lib/ui/app-columns-layout";
import { useDashboardNotification } from "@saleor/apps-shared";
import { Box, BoxProps, Text, Input, Button } from "@saleor/macaw-ui/next";
import { Breadcrumbs, ButtonsBox, Layout, SkeletonLayout, TextLink } from "@saleor/apps-ui";
import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
import { useAppApi } from "../hooks/useAppApi";
interface ConfigurationField {
key: string;
value: string;
}
function Section(props: BoxProps) {
return <Box padding={4} {...props} />;
}
function Instructions() {
const { appBridge } = useAppBridge();
const openExternalUrl = (url: string) => {
// eslint-disable-next-line
appBridge?.dispatch({
type: "redirect",
payload: {
newContext: true,
actionId: "redirect_from_klaviyo_app",
to: url,
},
});
};
return (
<Section>
<Text as={"h3"} variant="heading">
<Box>
<Text as={"h3"} variant="heading" marginY={4}>
How to set up
</Text>
<Text as="p">App will send events as Klaviyo metrics each time Saleor Event occurs.</Text>
<Text as="p">
<Text as="p" marginBottom={2}>
App will send events as Klaviyo metrics each time Saleor Event occurs.
</Text>
<Text as="p" marginBottom={2}>
When first metric is sent, it should be available in Klaviyo to build on top of.
</Text>
<Text as="p">
<Text as="p" marginBottom={4}>
Metric name can be customized, PUBLIC_TOKEN must be provided to enable the app.
</Text>
<Text as={"h3"} variant="heading">
@ -49,16 +33,9 @@ function Instructions() {
</Text>
<ul>
<li>
<a
onClick={(e) => {
e.preventDefault();
openExternalUrl("https://github.com/saleor/saleor-app-klaviyo");
}}
href="https://github.com/saleor/saleor-app-klaviyo"
>
<TextLink href="https://github.com/saleor/saleor-app-klaviyo" newTab>
Visit repository & readme
</a>
</TextLink>
</li>
</ul>
<Text as={"h3"} variant="heading">
@ -66,47 +43,28 @@ function Instructions() {
</Text>
<ul>
<li>
<a
onClick={(e) => {
e.preventDefault();
openExternalUrl(
"https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys"
);
}}
<TextLink
href="https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys"
newTab
>
Read about public tokens
</a>
</TextLink>
</li>
<li>
<a
onClick={(e) => {
e.preventDefault();
openExternalUrl("https://www.klaviyo.com/account#api-keys-tab");
}}
href="https://www.klaviyo.com/account#api-keys-tab"
>
<TextLink href="https://www.klaviyo.com/account#api-keys-tab" newTab>
Get public token here
</a>
</TextLink>
</li>
<li>
<a
onClick={(e) => {
e.preventDefault();
openExternalUrl(
"https://help.klaviyo.com/hc/en-us/articles/115005076787-Guide-to-Managing-Your-Metrics"
);
}}
<TextLink
href="https://help.klaviyo.com/hc/en-us/articles/115005076787-Guide-to-Managing-Your-Metrics"
newTab
>
Read about metrics
</a>
</TextLink>
</li>
</ul>
</Section>
</Box>
);
}
@ -114,6 +72,7 @@ function Configuration() {
const { appBridgeState } = useAppBridge();
const { notifySuccess, notifyError } = useDashboardNotification();
const [configuration, setConfiguration] = useState<ConfigurationField[]>();
const authenticatedFetch = useAuthenticatedFetch() as typeof window.fetch;
const { data: configurationData, error } = useAppApi({
url: "/api/configuration",
@ -131,13 +90,8 @@ function Configuration() {
const handleSubmit = (event: SyntheticEvent) => {
event.preventDefault();
fetch("/api/configuration", {
authenticatedFetch("/api/configuration", {
method: "POST",
headers: [
["content-type", "application/json"],
[SALEOR_API_URL_HEADER, appBridgeState?.saleorApiUrl!],
[SALEOR_AUTHORIZATION_BEARER_HEADER, appBridgeState?.token!],
],
body: JSON.stringify({ data: configuration }),
})
.then(async (response) => {
@ -149,7 +103,7 @@ function Configuration() {
})
.catch(async () => {
await notifyError(
"Configuration update failed. Ensure fields are filled correctly and you have MANAGE_APPS permission"
"Configuration update failed. Ensure fields are filled correctly and you have MANAGE_APPS permission",
);
});
};
@ -158,7 +112,7 @@ function Configuration() {
const { name, value } = event.target as HTMLInputElement;
setConfiguration((prev) =>
prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField))
prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField)),
);
};
@ -192,31 +146,36 @@ function Configuration() {
}
if (configuration === undefined) {
return <p>Loading...</p>;
return <SkeletonLayout.Section />;
}
return (
<AppColumnsLayout>
<div />
<Section>
<Text variant={"heading"} marginBottom={4} as={"h2"}>
Klaviyo configuration
</Text>
<Box as={"form"} display={"grid"} gap={4} gridAutoFlow={"row"} onSubmit={handleSubmit}>
{configuration!.map(({ key, value }) => (
<div key={key}>
<Input label={key} name={key} onChange={onChange} value={value} />
</div>
))}
<div>
<Button type="submit" variant="primary">
Save
</Button>
</div>
</Box>
</Section>
<Instructions />
</AppColumnsLayout>
<Box>
<Breadcrumbs marginBottom={10}>
<Breadcrumbs.Item>Configuration</Breadcrumbs.Item>
</Breadcrumbs>
<Layout.AppSection heading="Set up integration" sideContent={<Instructions />}>
<Layout.AppSectionCard
as={"form"}
onSubmit={handleSubmit}
footer={
<ButtonsBox>
<Button type="submit" variant="primary">
Save
</Button>
</ButtonsBox>
}
>
<Box display={"grid"} gap={4} gridAutoFlow={"row"}>
{configuration!.map(({ key, value }) => (
<div key={key}>
<Input label={key} name={key} onChange={onChange} value={value} />
</div>
))}
</Box>
</Layout.AppSectionCard>
</Layout.AppSection>
</Box>
);
}

View file

@ -18,7 +18,9 @@
"SENTRY_PROJECT",
"SENTRY_AUTH_TOKEN",
"NEXT_PUBLIC_VERCEL_ENV",
"SENTRY_ENVIRONMENT"
"SENTRY_ENVIRONMENT",
"APP_API_BASE_URL",
"APP_IFRAME_BASE_URL"
]
}
}

View file

@ -1,5 +1,55 @@
# saleor-app-products-feed
## 1.12.2
### Patch Changes
- ae6dbb1: Removed webhooks on product changes used for feed cache due to changed max execution time.
- ae6dbb1: Changed Vercel's maximum execution time to be 5 minutes for feed generation. This should help with the previous limits of 60s, that was not enough for feed to be generated.
## 1.12.1
### 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.12.0
### Minor Changes
- 261e9d1: Added additional images attribute to the feed for media uploaded to the product.
### Patch Changes
- 23e71bc: Fix typo on button label
- 261e9d1: Improved default resolution of the submitted images. Was: 500px, now it's 1024px.
Users can now configure the size in the app configuration.
- 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.11.4
### Patch Changes
- 2a1385b: Plaintext EditorJS renderer has been moved to the shared package.
- 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.11.3
### Patch Changes

View file

@ -32,6 +32,21 @@ fragment GoogleFeedProductVariant on ProductVariant {
slug
description
seoDescription
media{
id
alt
url(size: $imageSize)
type
}
variants{
id
media{
id
alt
url(size: $imageSize)
type
}
}
attributes{
attribute{
id
@ -41,7 +56,7 @@ fragment GoogleFeedProductVariant on ProductVariant {
name
}
}
thumbnail {
thumbnail(size: $imageSize) {
url
}
category {

View file

@ -1,8 +0,0 @@
fragment ProductVariantWebhookPayload on ProductVariant {
channel
channelListings {
channel {
slug
}
}
}

View file

@ -1,8 +0,0 @@
fragment ProductWebhookPayload on Product {
channel
channelListings {
channel {
slug
}
}
}

View file

@ -1,4 +1,4 @@
query FetchProductDataForFeed($first:Int!, $after: String, $channel: String!){
query FetchProductDataForFeed($first:Int!, $after: String, $channel: String!, $imageSize: Int = 1024){
productVariants(first:$first, after: $after, channel: $channel){
pageInfo{
hasNextPage

View file

@ -1,9 +0,0 @@
subscription ProductCreated {
event {
... on ProductCreated {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductDeleted {
event {
... on ProductDeleted {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductUpdated {
event {
... on ProductUpdated {
product {
...ProductWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductVariantCreated {
event {
... on ProductVariantCreated {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -1,10 +0,0 @@
subscription ProductVariantDeleted {
event {
... on ProductVariantDeleted {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -1,9 +0,0 @@
subscription ProductVariantUpdated {
event {
... on ProductVariantUpdated {
productVariant {
...ProductVariantWebhookPayload
}
}
}
}

View file

@ -6,7 +6,12 @@ const isSentryPropertiesInEnvironment =
/** @type {import('next').NextConfig} */
const nextConfig = {
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/webhook-utils",
],
};
const configWithSentry = withSentryConfig(
@ -22,7 +27,7 @@ const configWithSentry = withSentryConfig(
tunnelRoute: "/monitoring",
hideSourceMaps: true,
disableLogger: true,
}
},
);
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;

View file

@ -1,6 +1,6 @@
{
"name": "saleor-app-products-feed",
"version": "1.11.3",
"version": "1.12.2",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
@ -13,21 +13,22 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.332.0",
"@hookform/resolvers": "^3.1.0",
"@saleor/app-sdk": "0.41.1",
"@hookform/resolvers": "^3.3.1",
"@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.118",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*",
"@sentry/nextjs": "7.55.2",
"@saleor/webhook-utils": "workspace:*",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19",
"@trpc/client": "10.34.0",
"@trpc/next": "10.34.0",
"@trpc/react-query": "10.34.0",
"@trpc/server": "10.34.0",
"@trpc/client": "10.38.1",
"@trpc/next": "10.38.1",
"@trpc/react-query": "10.38.1",
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"clsx": "^1.2.1",
"dotenv": "^16.3.1",
"fast-xml-parser": "^4.0.15",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
@ -47,7 +48,7 @@
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/schema-ast": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
@ -55,7 +56,7 @@
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3",
"@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",
"@types/react": "18.2.5",
"@types/react-dom": "18.2.5",

View file

@ -0,0 +1,7 @@
# Webhook migration scripts
Test migration with dry run, operation will not modify any data:
`npx tsx scripts/migrations/run-webhooks-migration-dry-run.ts`
To start the migration run command:
`npx tsx scripts/migrations/run-webhooks-migration.ts`

View file

@ -0,0 +1,20 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { SaleorCloudAPL } from "@saleor/app-sdk/APL";
export const verifyRequiredEnvs = () => {
const requiredEnvs = ["SALEOR_CLOUD_TOKEN", "SALEOR_CLOUD_RESOURCE_URL"];
if (!requiredEnvs.every((env) => process.env[env])) {
throw new Error(`Missing envs: ${requiredEnvs.join(" | ")}`);
}
};
export const fetchCloudAplEnvs = () => {
const saleorAPL = new SaleorCloudAPL({
token: process.env.SALEOR_CLOUD_TOKEN!,
resourceUrl: process.env.SALEOR_CLOUD_RESOURCE_URL!,
});
return saleorAPL.getAll();
};

View file

@ -0,0 +1,30 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { updateWebhooksScript } from "./update-webhooks";
dotenv.config();
const runMigration = async () => {
console.log("Starting webhooks migration (dry run)");
verifyRequiredEnvs();
console.log("Envs verified, fetching envs");
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
console.error("Could not fetch instances from the APL");
console.error(r);
process.exit(1);
});
for (const env of allEnvs) {
await updateWebhooksScript({ authData: env, dryRun: true });
}
console.log("Migration dry run complete");
};
runMigration();

View file

@ -0,0 +1,30 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import * as dotenv from "dotenv";
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
import { updateWebhooksScript } from "./update-webhooks";
dotenv.config();
const runMigration = async () => {
console.log("Starting running migration");
verifyRequiredEnvs();
console.log("Envs verified, fetching envs");
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
console.error("Could not fetch instances from the APL");
console.error(r);
process.exit(1);
});
for (const env of allEnvs) {
await updateWebhooksScript({ authData: env, dryRun: false });
}
console.log("Migration complete");
};
runMigration();

View file

@ -0,0 +1,29 @@
/* eslint-disable turbo/no-undeclared-env-vars */
import { createGraphQLClient } from "@saleor/apps-shared";
import { AuthData } from "@saleor/app-sdk/APL";
import { webhookMigrationRunner } from "@saleor/webhook-utils";
export const updateWebhooksScript = async ({
authData,
dryRun,
}: {
authData: AuthData;
dryRun: boolean;
}) => {
console.log("Working on env: ", authData.saleorApiUrl);
const client = createGraphQLClient({
saleorApiUrl: authData.saleorApiUrl,
token: authData.token,
});
await webhookMigrationRunner({
client,
dryRun,
getManifests: async ({ appDetails }) => {
// Products feed application has currently no webhooks, so we return empty array
return [];
},
});
};

View file

@ -28,11 +28,14 @@ const exampleAttributeMappingConfig: RootConfig["attributeMapping"] = {
const exampleTitleTemplate: RootConfig["titleTemplate"] =
"Example {{ variant.product.name }} - {{ variant.name }}";
const exampleImageSize: RootConfig["imageSize"] = 1024;
const exampleConfiguration: RootConfig = {
channelConfig: exampleChannelConfig,
s3: exampleS3Config,
attributeMapping: exampleAttributeMappingConfig,
titleTemplate: exampleTitleTemplate,
imageSize: exampleImageSize,
};
describe("AppConfig", function () {
@ -51,6 +54,7 @@ describe("AppConfig", function () {
sizeAttributeIds: [],
},
titleTemplate: "{{variant.product.name}} - {{variant.name}}",
imageSize: 1024,
});
});
@ -60,13 +64,15 @@ describe("AppConfig", function () {
expect(instance.getRootConfig()).toEqual(exampleConfiguration);
});
it("Fill attribute mapping and title template with default values, when initial data are lacking those fields", () => {
it("Fill attribute mapping, image size and title template with default values, when initial data are lacking those fields", () => {
const configurationWithoutMapping = structuredClone(exampleConfiguration);
// @ts-expect-error: Simulating data before the migration
delete configurationWithoutMapping.attributeMapping;
// @ts-expect-error
delete configurationWithoutMapping.titleTemplate;
// @ts-expect-error
delete configurationWithoutMapping.imageSize;
const instance = new AppConfig(configurationWithoutMapping as any); // Casting used to prevent TS from reporting an error
@ -80,6 +86,7 @@ describe("AppConfig", function () {
sizeAttributeIds: [],
},
titleTemplate: "{{variant.product.name}} - {{variant.name}}",
imageSize: 1024,
});
});
@ -110,6 +117,7 @@ describe("AppConfig", function () {
sizeAttributeIds: [],
},
titleTemplate: "{{ variant.name }}",
imageSize: 1024,
});
const serialized = instance1.serialize();
@ -132,6 +140,7 @@ describe("AppConfig", function () {
sizeAttributeIds: [],
},
titleTemplate: "{{ variant.name }}",
imageSize: 1024,
});
});
});
@ -160,6 +169,7 @@ describe("AppConfig", function () {
sizeAttributeIds: ["size-id"],
},
titleTemplate: "{{ variant.product.name }} - {{ variant.name }}",
imageSize: 1024,
});
it("getRootConfig returns root config data", () => {
@ -186,6 +196,7 @@ describe("AppConfig", function () {
sizeAttributeIds: ["size-id"],
},
titleTemplate: "{{ variant.product.name }} - {{ variant.name }}",
imageSize: 1024,
});
});

View file

@ -1,6 +1,14 @@
import { createLogger } from "@saleor/apps-shared";
import { z } from "zod";
const imageSizeFieldSchema = z.coerce.number().gte(256).default(1024);
export const imageSizeInputSchema = z.object({
imageSize: imageSizeFieldSchema,
});
export type ImageSizeInput = z.infer<typeof imageSizeInputSchema>;
const titleTemplateFieldSchema = z.string().default("{{variant.product.name}} - {{variant.name}}");
export const titleTemplateInputSchema = z.object({
@ -34,6 +42,7 @@ const rootAppConfigSchema = z.object({
titleTemplate: titleTemplateFieldSchema
.optional()
.default(titleTemplateFieldSchema.parse(undefined)),
imageSize: imageSizeFieldSchema.optional().default(imageSizeFieldSchema.parse(undefined)),
attributeMapping: attributeMappingSchema
.nullable()
.optional()
@ -60,6 +69,7 @@ export class AppConfig {
s3: null,
attributeMapping: attributeMappingSchema.parse({}),
titleTemplate: titleTemplateFieldSchema.parse(undefined),
imageSize: imageSizeFieldSchema.parse(undefined),
};
constructor(initialData?: RootConfig) {
@ -147,4 +157,14 @@ export class AppConfig {
getTitleTemplate() {
return this.rootData.titleTemplate;
}
setImageSize(imageSize: z.infer<typeof imageSizeFieldSchema>) {
this.rootData.imageSize = imageSize;
return this;
}
getImageSize() {
return this.rootData.imageSize;
}
}

View file

@ -2,8 +2,7 @@ import { router } from "../trpc/trpc-server";
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
import { createLogger } from "@saleor/apps-shared";
import { updateCacheForConfigurations } from "../metadata-cache/update-cache-for-configurations";
import { AppConfigSchema, titleTemplateInputSchema } from "./app-config";
import { AppConfigSchema, imageSizeInputSchema, titleTemplateInputSchema } from "./app-config";
import { z } from "zod";
import { createS3ClientFromConfiguration } from "../file-storage/s3/create-s3-client-from-configuration";
import { checkBucketAccess } from "../file-storage/s3/check-bucket-access";
@ -106,17 +105,6 @@ export const appConfigurationRouter = router({
}) => {
const config = await getConfig();
/**
* TODO Check if this has to run, once its cached, it should be invalidated by webhooks only.
*
* But this operation isn't expensive and users will not continuously save this form
*/
await updateCacheForConfigurations({
client: apiClient,
channelsSlugs: [input.channelSlug],
saleorApiUrl: saleorApiUrl,
});
logger.debug({ channel: input.channelSlug }, "Updated cache for channel");
config.setChannelUrls(input.channelSlug, input.urls);
@ -158,6 +146,21 @@ export const appConfigurationRouter = router({
return result;
}),
setImageSize: protectedClientProcedure
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
.input(imageSizeInputSchema)
.mutation(async ({ ctx: { getConfig, appConfigMetadataManager, logger }, input }) => {
logger.debug("Setting image size");
const config = await getConfig();
config.setImageSize(input.imageSize);
await appConfigMetadataManager.set(config.serialize());
logger.debug("image size set");
return null;
}),
setTitleTemplate: protectedClientProcedure
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
.input(titleTemplateInputSchema)

View file

@ -0,0 +1,98 @@
import { ImageSizeInput, imageSizeInputSchema } from "./app-config";
import { useForm } from "react-hook-form";
import { Box, Button, Text } from "@saleor/macaw-ui/next";
import React, { useCallback, useMemo } from "react";
import { Select } from "@saleor/react-hook-form-macaw";
import { zodResolver } from "@hookform/resolvers/zod";
import { trpcClient } from "../trpc/trpc-client";
import { useDashboardNotification } from "@saleor/apps-shared";
type Props = {
initialData: ImageSizeInput;
onSubmit(data: ImageSizeInput): Promise<void>;
};
const imageSizeOptions = [
{ value: "256", label: "256px" },
{ value: "512", label: "512px" },
{ value: "1024", label: "1024px" },
{ value: "2048", label: "2048px" },
{ value: "4096", label: "4096px" },
];
export const ImageConfigurationForm = (props: Props) => {
const { handleSubmit, control, formState } = useForm<ImageSizeInput>({
defaultValues: props.initialData,
resolver: zodResolver(imageSizeInputSchema),
});
return (
<Box
as={"form"}
display={"flex"}
gap={5}
flexDirection={"column"}
onSubmit={handleSubmit(props.onSubmit)}
>
<Select control={control} name="imageSize" label="Image size" options={imageSizeOptions} />
{!!formState.errors.imageSize?.message && (
<Text variant="caption" color={"textCriticalSubdued"}>
{formState.errors.imageSize?.message}
</Text>
)}
<Box display={"flex"} flexDirection={"row"} gap={4} justifyContent={"flex-end"}>
<Button type="submit" variant="primary">
Save
</Button>
</Box>
</Box>
);
};
export const ConnectedImageConfigurationForm = () => {
const { notifyError, notifySuccess } = useDashboardNotification();
const { data, isLoading } = trpcClient.appConfiguration.fetch.useQuery();
const { mutate } = trpcClient.appConfiguration.setImageSize.useMutation({
onSuccess() {
notifySuccess("Success", "Updated image size");
},
onError() {
notifyError("Error", "Failed to update, please refresh and try again");
},
});
const handleSubmit = useCallback(
async (data: ImageSizeInput) => {
mutate(data);
},
[mutate],
);
const formData: ImageSizeInput = useMemo(() => {
if (data?.imageSize) {
return {
imageSize: data.imageSize,
};
}
return imageSizeInputSchema.parse({});
}, [data]);
if (isLoading) {
return <Text>Loading...</Text>;
}
return (
<>
{!isLoading ? (
<ImageConfigurationForm onSubmit={handleSubmit} initialData={formData} />
) : (
<Box>Loading</Box>
)}
</>
);
};

Some files were not shown because too many files have changed in this diff Show more