Merge branch 'master' into SALEOR-1738-Tests-for-home-page
This commit is contained in:
commit
68eb5309ef
330 changed files with 14422 additions and 6283 deletions
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -21,10 +21,12 @@ greatly reduce the amount of work needed to review your work. -->
|
||||||
1. [ ] Data-test are added for new elements.
|
1. [ ] Data-test are added for new elements.
|
||||||
1. [ ] Type definitions are up to date.
|
1. [ ] Type definitions are up to date.
|
||||||
1. [ ] Changes are mentioned in the changelog.
|
1. [ ] Changes are mentioned in the changelog.
|
||||||
|
1. [ ] The changes are tested in different browsers (Chrome, Firefox, Safari).
|
||||||
|
1. [ ] The changes are tested in light and dark mode.
|
||||||
|
|
||||||
### Test environment config
|
### Test environment config
|
||||||
|
|
||||||
<!-- Do not remove this section. It is required to properly setup test instance.
|
<!-- Do not remove this section. It is required to properly setup test instance.
|
||||||
Modify API_URI if you want test instance to use custom backend. -->
|
Modify API_URI if you want test instance to use custom backend. -->
|
||||||
|
|
||||||
API_URI=https://master.staging.saleor.rocks/graphql/
|
API_URI=https://master.staging.saleor.cloud/graphql/
|
||||||
|
|
7
.github/workflows/deploy-staging.yaml
vendored
7
.github/workflows/deploy-staging.yaml
vendored
|
@ -11,6 +11,12 @@ jobs:
|
||||||
API_URI: https://master.staging.saleor.cloud/graphql/
|
API_URI: https://master.staging.saleor.cloud/graphql/
|
||||||
APP_MOUNT_URI: /dashboard/
|
APP_MOUNT_URI: /dashboard/
|
||||||
STATIC_URL: /dashboard/static/
|
STATIC_URL: /dashboard/static/
|
||||||
|
SENTRY_ORG: saleor
|
||||||
|
SENTRY_PROJECT: dashboard
|
||||||
|
SENTRY_URL_PREFIX: "~/dashboard/static"
|
||||||
|
ENVIRONMENT: master-staging
|
||||||
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Package
|
- name: Package
|
||||||
|
@ -41,4 +47,3 @@ jobs:
|
||||||
aws s3 sync build/dashboard s3://${{ secrets.AWS_STAGING_DEPLOYMENT_BUCKET }}/saleor-master-staging/static/
|
aws s3 sync build/dashboard s3://${{ secrets.AWS_STAGING_DEPLOYMENT_BUCKET }}/saleor-master-staging/static/
|
||||||
aws s3 cp build/dashboard/index.html s3://${{ secrets.AWS_STAGING_DEPLOYMENT_BUCKET }}/saleor-master-staging/
|
aws s3 cp build/dashboard/index.html s3://${{ secrets.AWS_STAGING_DEPLOYMENT_BUCKET }}/saleor-master-staging/
|
||||||
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_STAGING_CF_DIST_ID }} --paths "/dashboard*"
|
aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_STAGING_CF_DIST_ID }} --paths "/dashboard*"
|
||||||
|
|
||||||
|
|
4
.github/workflows/test-env-deploy.yml
vendored
4
.github/workflows/test-env-deploy.yml
vendored
|
@ -59,7 +59,7 @@ jobs:
|
||||||
- name: Run build
|
- name: Run build
|
||||||
env:
|
env:
|
||||||
# Use custom API_URI or the default one
|
# Use custom API_URI or the default one
|
||||||
API_URI: ${{ steps.api_uri.outputs.custom_api_uri || 'https://master.staging.saleor.rocks/graphql/' }}
|
API_URI: ${{ steps.api_uri.outputs.custom_api_uri || 'https://master.staging.saleor.cloud/graphql/' }}
|
||||||
APP_MOUNT_URI: /
|
APP_MOUNT_URI: /
|
||||||
STATIC_URL: /
|
STATIC_URL: /
|
||||||
run: |
|
run: |
|
||||||
|
@ -88,7 +88,7 @@ jobs:
|
||||||
aws s3 sync ./build/storybook s3://${{ secrets.AWS_TEST_DEPLOYMENT_BUCKET }}/${{ steps.set-domain.outputs.domain }}/storybook
|
aws s3 sync ./build/storybook s3://${{ secrets.AWS_TEST_DEPLOYMENT_BUCKET }}/${{ steps.set-domain.outputs.domain }}/storybook
|
||||||
|
|
||||||
- name: Invalidate cache
|
- name: Invalidate cache
|
||||||
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_TEST_CF_DIST_ID }} --paths "/${{ env.domain }}/*"
|
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_TEST_CF_DIST_ID }} --paths "/${{ steps.set-domain.outputs.domain }}/*"
|
||||||
|
|
||||||
- name: Update deployment status
|
- name: Update deployment status
|
||||||
uses: bobheadxi/deployments@v0.4.2
|
uses: bobheadxi/deployments@v0.4.2
|
||||||
|
|
|
@ -9,9 +9,16 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Add shipping methods to translation section - #864 by @marekchoinski
|
- Add shipping methods to translation section - #864 by @marekchoinski
|
||||||
- New Miscellaneous and Product refunds - #870 by @orzechdev
|
- New Miscellaneous and Product refunds - #870 by @orzechdev
|
||||||
- Add zip code exclusion - #877 by @dominik-zeglen
|
- Add zip code exclusion - #877 by @dominik-zeglen
|
||||||
|
- Add order reissue
|
||||||
- Update quantity column in Inventory part of Product Variant view - #904 by @dominik-zeglen
|
- Update quantity column in Inventory part of Product Variant view - #904 by @dominik-zeglen
|
||||||
- Add file attributes - #884 by @orzechdev
|
- Add file attributes - #884 by @orzechdev
|
||||||
- Add shipping delivery days - #914 by @orzechdev
|
- Add shipping delivery days - #914 by @orzechdev
|
||||||
|
- Guard against non-staff users logging in - #947 by @jwm0
|
||||||
|
- Add reference attributes - #917 by @orzechdev
|
||||||
|
- Add product reference attributes - #948 by @orzechdev
|
||||||
|
- Drop descriptionJson and contentJson fields - #950 by @jwm0
|
||||||
|
- Add error tracking with Sentry adapter - #956 by @jwm0
|
||||||
|
- Add OAuth2 login with OpenID support - #963 by @orzechdev
|
||||||
|
|
||||||
# 2.11.1
|
# 2.11.1
|
||||||
|
|
||||||
|
|
41
README.md
41
README.md
|
@ -61,7 +61,7 @@ $ npm i
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
There are two environment variables available for configuration:
|
There following environment variables are available for configuration:
|
||||||
|
|
||||||
- `API_URI` (required) - URI of a running instance of Saleor GraphQL API.
|
- `API_URI` (required) - URI of a running instance of Saleor GraphQL API.
|
||||||
If you are running Saleor locally with the default settings, set `API_URI` to: `http://localhost:8000/graphql/`.
|
If you are running Saleor locally with the default settings, set `API_URI` to: `http://localhost:8000/graphql/`.
|
||||||
|
@ -88,4 +88,43 @@ To build the application bundle run:
|
||||||
$ npm run build
|
$ npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Error Tracking
|
||||||
|
|
||||||
|
Saleor Dashboard is using a generic error tracking wrapper function that takes care of the most popular use cases:
|
||||||
|
|
||||||
|
- initializing the tracker
|
||||||
|
- capturing exceptions and (optionally) displaying the event id
|
||||||
|
- setting basic user data (this is opt-in and disabled by default)
|
||||||
|
|
||||||
|
By default it ships with a Sentry adapter but any kind of error tracking software can be used by creating a custom adapter (using Sentry and TS types as an example).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/services/errorTracking/index.ts
|
||||||
|
|
||||||
|
import { CustomAdapter } from "./adapters/";
|
||||||
|
|
||||||
|
const errorTracker = ErrorTrackerFactory(CustomAdapter(config));
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Usage with Sentry adapter:
|
||||||
|
|
||||||
|
Sentry is used as the default tracker so no changes in code are necessary and the configuration is done via environment variables.
|
||||||
|
|
||||||
|
The following environment variables are available:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Required
|
||||||
|
SENTRY_DSN=
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
# https://docs.sentry.io/product/cli/configuration/
|
||||||
|
SENTRY_AUTH_TOKEN=
|
||||||
|
SENTRY_ORG=
|
||||||
|
SENTRY_PROJECT=
|
||||||
|
SENTRY_URL_PREFIX=
|
||||||
|
ENVIRONMENT=
|
||||||
|
```
|
||||||
|
|
||||||
#### Crafted with ❤️ by [Mirumee Software](https://mirumee.com)
|
#### Crafted with ❤️ by [Mirumee Software](https://mirumee.com)
|
||||||
|
|
|
@ -3,6 +3,14 @@
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Cancel Order"
|
"string": "Cancel Order"
|
||||||
},
|
},
|
||||||
|
"amount title": {
|
||||||
|
"context": "amount title",
|
||||||
|
"string": "Refunded amount"
|
||||||
|
},
|
||||||
|
"by preposition": {
|
||||||
|
"context": "by preposition",
|
||||||
|
"string": "by"
|
||||||
|
},
|
||||||
"configurationMenuAttributes": {
|
"configurationMenuAttributes": {
|
||||||
"string": "Determine attributes used to create product types"
|
"string": "Determine attributes used to create product types"
|
||||||
},
|
},
|
||||||
|
@ -42,6 +50,42 @@
|
||||||
"configurationPluginsPages": {
|
"configurationPluginsPages": {
|
||||||
"string": "View and update your plugins and their settings."
|
"string": "View and update your plugins and their settings."
|
||||||
},
|
},
|
||||||
|
"event products list title refunded": {
|
||||||
|
"context": "refunded products list title",
|
||||||
|
"string": "Products refunded"
|
||||||
|
},
|
||||||
|
"event products list title replaced": {
|
||||||
|
"context": "replaced products list title",
|
||||||
|
"string": "Products replaced"
|
||||||
|
},
|
||||||
|
"event products list title returned": {
|
||||||
|
"context": "returned products list title",
|
||||||
|
"string": "Products returned"
|
||||||
|
},
|
||||||
|
"event products title draft reissued": {
|
||||||
|
"context": "draft created from replace products list title",
|
||||||
|
"string": "Products replaced"
|
||||||
|
},
|
||||||
|
"event title draft reissued": {
|
||||||
|
"context": "draft created from replace event title",
|
||||||
|
"string": "Draft was reissued from order "
|
||||||
|
},
|
||||||
|
"event title marked as paid": {
|
||||||
|
"context": "order marked as paid event title",
|
||||||
|
"string": "Order was marked as paid by"
|
||||||
|
},
|
||||||
|
"event title refunded": {
|
||||||
|
"context": "refunded event title",
|
||||||
|
"string": "Products were refunded by "
|
||||||
|
},
|
||||||
|
"event title replaced": {
|
||||||
|
"context": "replaced event title",
|
||||||
|
"string": "Products were replaced by "
|
||||||
|
},
|
||||||
|
"event title returned": {
|
||||||
|
"context": "returned event title",
|
||||||
|
"string": "Products were returned by"
|
||||||
|
},
|
||||||
"homeActivityCardHeader": {
|
"homeActivityCardHeader": {
|
||||||
"context": "header",
|
"context": "header",
|
||||||
"string": "Activity"
|
"string": "Activity"
|
||||||
|
@ -336,6 +380,10 @@
|
||||||
"context": "unassign product from sale, button",
|
"context": "unassign product from sale, button",
|
||||||
"string": "Unassign"
|
"string": "Unassign"
|
||||||
},
|
},
|
||||||
|
"shipment refund title": {
|
||||||
|
"context": "shipment refund title",
|
||||||
|
"string": "Shipment was refunded"
|
||||||
|
},
|
||||||
"shippingZoneDetailsDialogsDeleteShippingMethod": {
|
"shippingZoneDetailsDialogsDeleteShippingMethod": {
|
||||||
"context": "delete shipping method",
|
"context": "delete shipping method",
|
||||||
"string": "Are you sure you want to delete {name}?"
|
"string": "Are you sure you want to delete {name}?"
|
||||||
|
@ -733,37 +781,53 @@
|
||||||
"context": "dialog content",
|
"context": "dialog content",
|
||||||
"string": "Are you sure you want to delete {attributeName}?"
|
"string": "Are you sure you want to delete {attributeName}?"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_1005562666": {
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_attributeLabel": {
|
||||||
"context": "attribute's editor component",
|
"context": "attribute's label",
|
||||||
"string": "Catalog Input type for Store Owner"
|
"string": "Default Label"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_1336738461": {
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_attributeSlug": {
|
||||||
"context": "product attribute type",
|
|
||||||
"string": "Dropdown"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_1376373679": {
|
|
||||||
"context": "file attribute type",
|
|
||||||
"string": "File"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_2592224946": {
|
|
||||||
"context": "check to require attribute to have value",
|
|
||||||
"string": "Value Required"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_3334509011": {
|
|
||||||
"context": "product attribute type",
|
|
||||||
"string": "Multiple Select"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_3605174225": {
|
|
||||||
"context": "attribute's slug short code label",
|
"context": "attribute's slug short code label",
|
||||||
"string": "Attribute Code"
|
"string": "Attribute Code"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_4107478955": {
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_attributeSlugHelperText": {
|
||||||
"context": "attribute slug input field helper text",
|
"context": "attribute slug input field helper text",
|
||||||
"string": "This is used internally. Make sure you don’t use spaces"
|
"string": "This is used internally. Make sure you don’t use spaces"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_691600601": {
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_dropdown": {
|
||||||
"context": "attribute's label",
|
"context": "product attribute type",
|
||||||
"string": "Default Label"
|
"string": "Dropdown"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_entityType": {
|
||||||
|
"context": "attribute's editor component entity",
|
||||||
|
"string": "Entity"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_file": {
|
||||||
|
"context": "file attribute type",
|
||||||
|
"string": "File"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_inputType": {
|
||||||
|
"context": "attribute's editor component",
|
||||||
|
"string": "Catalog Input type for Store Owner"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_multiselect": {
|
||||||
|
"context": "product attribute type",
|
||||||
|
"string": "Multiple Select"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_page": {
|
||||||
|
"context": "page attribute entity type",
|
||||||
|
"string": "Pages"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_product": {
|
||||||
|
"context": "product attribute entity type",
|
||||||
|
"string": "Products"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_references": {
|
||||||
|
"context": "references attribute type",
|
||||||
|
"string": "References"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeDetails_dot_valueRequired": {
|
||||||
|
"context": "check to require attribute to have value",
|
||||||
|
"string": "Value Required"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeListPage_dot_2417065806": {
|
"src_dot_attributes_dot_components_dot_AttributeListPage_dot_2417065806": {
|
||||||
"context": "tab name",
|
"context": "tab name",
|
||||||
|
@ -844,43 +908,46 @@
|
||||||
"context": "page title",
|
"context": "page title",
|
||||||
"string": "Create New Attribute"
|
"string": "Create New Attribute"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_1318123158": {
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_availableInGrid": {
|
||||||
"context": "attribute is filterable in storefront",
|
|
||||||
"string": "Use in Faceted Navigation"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_1877630205": {
|
|
||||||
"context": "attribute properties regarding storefront",
|
|
||||||
"string": "Storefront Properties"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_26409543": {
|
|
||||||
"context": "attribute properties regarding dashboard",
|
|
||||||
"string": "Dashboard Properties"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_3135366329": {
|
|
||||||
"context": "attribute visibility in storefront",
|
|
||||||
"string": "Public"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_3590282519": {
|
|
||||||
"context": "attribute position in storefront filters",
|
|
||||||
"string": "Position in faceted navigation"
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_3758203740": {
|
|
||||||
"string": "If enabled, attribute will be accessible to customers."
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_4048785456": {
|
|
||||||
"string": "If enabled this attribute can be used as a column in product table."
|
|
||||||
},
|
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_673770329": {
|
|
||||||
"context": "add attribute as column in product list table",
|
"context": "add attribute as column in product list table",
|
||||||
"string": "Add to Column Options"
|
"string": "Add to Column Options"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_714335445": {
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_availableInGridCaption": {
|
||||||
|
"context": "caption",
|
||||||
|
"string": "If enabled this attribute can be used as a column in product table."
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_dashboardPropertiesTitle": {
|
||||||
|
"context": "attribute properties regarding dashboard",
|
||||||
|
"string": "Dashboard Properties"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_filterableInDashboard": {
|
||||||
"context": "use attribute in filtering",
|
"context": "use attribute in filtering",
|
||||||
"string": "Use in Filtering"
|
"string": "Use in Filtering"
|
||||||
},
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_787251583": {
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_filterableInDashboardCaption": {
|
||||||
|
"context": "caption",
|
||||||
"string": "If enabled, you’ll be able to use this attribute to filter products in product list."
|
"string": "If enabled, you’ll be able to use this attribute to filter products in product list."
|
||||||
},
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_filterableInStorefront": {
|
||||||
|
"context": "attribute is filterable in storefront",
|
||||||
|
"string": "Use in Faceted Navigation"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_storefrontPropertiesTitle": {
|
||||||
|
"context": "attribute properties regarding storefront",
|
||||||
|
"string": "Storefront Properties"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_storefrontSearchPosition": {
|
||||||
|
"context": "attribute position in storefront filters",
|
||||||
|
"string": "Position in faceted navigation"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_visibleInStorefront": {
|
||||||
|
"context": "attribute visibility in storefront",
|
||||||
|
"string": "Public"
|
||||||
|
},
|
||||||
|
"src_dot_attributes_dot_components_dot_AttributeProperties_dot_visibleInStorefrontCaption": {
|
||||||
|
"context": "caption",
|
||||||
|
"string": "If enabled, attribute will be accessible to customers."
|
||||||
|
},
|
||||||
"src_dot_attributes_dot_components_dot_AttributeValueDeleteDialog_dot_1326420604": {
|
"src_dot_attributes_dot_components_dot_AttributeValueDeleteDialog_dot_1326420604": {
|
||||||
"context": "delete attribute value",
|
"context": "delete attribute value",
|
||||||
"string": "Are you sure you want to delete \"{name}\" value?"
|
"string": "Are you sure you want to delete \"{name}\" value?"
|
||||||
|
@ -949,12 +1016,23 @@
|
||||||
"src_dot_auth_dot_components_dot_LoginPage_dot_2237029987": {
|
"src_dot_auth_dot_components_dot_LoginPage_dot_2237029987": {
|
||||||
"string": "Password"
|
"string": "Password"
|
||||||
},
|
},
|
||||||
|
"src_dot_auth_dot_components_dot_LoginPage_dot_2981302356": {
|
||||||
|
"context": "link",
|
||||||
|
"string": "Use this link to recover it"
|
||||||
|
},
|
||||||
"src_dot_auth_dot_components_dot_LoginPage_dot_3476994590": {
|
"src_dot_auth_dot_components_dot_LoginPage_dot_3476994590": {
|
||||||
"string": "Sorry, your username and/or password are incorrect. Please try again."
|
"string": "Sorry, your username and/or password are incorrect. Please try again."
|
||||||
},
|
},
|
||||||
"src_dot_auth_dot_components_dot_LoginPage_dot_4028609483": {
|
"src_dot_auth_dot_components_dot_LoginPage_dot_3762459576": {
|
||||||
"context": "button",
|
"context": "description",
|
||||||
"string": "Reset your password"
|
"string": "or login using"
|
||||||
|
},
|
||||||
|
"src_dot_auth_dot_components_dot_LoginPage_dot_534894384": {
|
||||||
|
"string": "Sorry, login went wrong. Please try again."
|
||||||
|
},
|
||||||
|
"src_dot_auth_dot_components_dot_LoginPage_dot_599516345": {
|
||||||
|
"context": "description",
|
||||||
|
"string": "Forgot password? {resetPasswordLink}"
|
||||||
},
|
},
|
||||||
"src_dot_auth_dot_components_dot_NewPasswordPage_dot_1254879564": {
|
"src_dot_auth_dot_components_dot_NewPasswordPage_dot_1254879564": {
|
||||||
"string": "New Password"
|
"string": "New Password"
|
||||||
|
@ -1502,6 +1580,18 @@
|
||||||
"src_dot_components_dot_AssignAttributeDialog_dot_902296540": {
|
"src_dot_components_dot_AssignAttributeDialog_dot_902296540": {
|
||||||
"string": "Search Attributes"
|
"string": "Search Attributes"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_AssignAttributeValueDialog_dot_header": {
|
||||||
|
"context": "dialog header",
|
||||||
|
"string": "Assign Attribute Value"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_AssignAttributeValueDialog_dot_searchLabel": {
|
||||||
|
"context": "label",
|
||||||
|
"string": "Search Attribute Value"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_AssignAttributeValueDialog_dot_searchPlaceholder": {
|
||||||
|
"context": "placeholder",
|
||||||
|
"string": "Search by value name, etc..."
|
||||||
|
},
|
||||||
"src_dot_components_dot_AssignCategoryDialog_dot_3125506097": {
|
"src_dot_components_dot_AssignCategoryDialog_dot_3125506097": {
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Assign Category"
|
"string": "Assign Category"
|
||||||
|
@ -1543,6 +1633,10 @@
|
||||||
"src_dot_components_dot_AttributeUnassignDialog_dot_2037985699": {
|
"src_dot_components_dot_AttributeUnassignDialog_dot_2037985699": {
|
||||||
"string": "Are you sure you want to unassign {attributeName} from {itemTypeName}?"
|
"string": "Are you sure you want to unassign {attributeName} from {itemTypeName}?"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_Attributes_dot_3824528779": {
|
||||||
|
"context": "button label",
|
||||||
|
"string": "Assign references"
|
||||||
|
},
|
||||||
"src_dot_components_dot_Attributes_dot_attributesNumber": {
|
"src_dot_components_dot_Attributes_dot_attributesNumber": {
|
||||||
"context": "number of attributes",
|
"context": "number of attributes",
|
||||||
"string": "{number} Attributes"
|
"string": "{number} Attributes"
|
||||||
|
@ -3032,6 +3126,9 @@
|
||||||
"src_dot_hooks_dot_3382262667": {
|
"src_dot_hooks_dot_3382262667": {
|
||||||
"string": "Variant {name} has been set as default."
|
"string": "Variant {name} has been set as default."
|
||||||
},
|
},
|
||||||
|
"src_dot_insufficientPermissions": {
|
||||||
|
"string": "Insufficient permissions"
|
||||||
|
},
|
||||||
"src_dot_lastName": {
|
"src_dot_lastName": {
|
||||||
"string": "Last Name"
|
"string": "Last Name"
|
||||||
},
|
},
|
||||||
|
@ -3130,13 +3227,17 @@
|
||||||
"src_dot_orders_dot_components_dot_OrderCustomer_dot_4282475982": {
|
"src_dot_orders_dot_components_dot_OrderCustomer_dot_4282475982": {
|
||||||
"string": "Billing Address"
|
"string": "Billing Address"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_1854613983": {
|
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_cancelOrder": {
|
||||||
"context": "button",
|
"context": "cancel button",
|
||||||
"string": "Cancel order"
|
"string": "Cancel order"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_3086420445": {
|
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_confirmOrder": {
|
||||||
"context": "save button",
|
"context": "save button",
|
||||||
"string": "confirm order"
|
"string": "Confirm order"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderDetailsPage_dot_returnOrder": {
|
||||||
|
"context": "return button",
|
||||||
|
"string": "Return / Replace order"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderDraftCancelDialog_dot_1961675716": {
|
"src_dot_orders_dot_components_dot_OrderDraftCancelDialog_dot_1961675716": {
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
|
@ -3272,6 +3373,37 @@
|
||||||
"context": "product's sku",
|
"context": "product's sku",
|
||||||
"string": "SKU"
|
"string": "SKU"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_1119771899": {
|
||||||
|
"context": "add tracking button",
|
||||||
|
"string": "Add tracking"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_2211099657": {
|
||||||
|
"context": "edit tracking button",
|
||||||
|
"string": "Edit tracking"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_2845258362": {
|
||||||
|
"context": "refund button",
|
||||||
|
"string": "Refund"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_3254150098": {
|
||||||
|
"string": "Tracking Number: {trackingNumber}"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_732594284": {
|
||||||
|
"context": "button",
|
||||||
|
"string": "Cancel Fulfillment"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_fulfilled": {
|
||||||
|
"context": "fulfillment group",
|
||||||
|
"string": "Fulfilled from: "
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_restocked": {
|
||||||
|
"context": "restocked group",
|
||||||
|
"string": "Restocked from: "
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderFulfilledProductsCard_dot_tracking": {
|
||||||
|
"context": "tracking number",
|
||||||
|
"string": "Tracking Number: {trackingNumber}"
|
||||||
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillmentCancelDialog_dot_1097287358": {
|
"src_dot_orders_dot_components_dot_OrderFulfillmentCancelDialog_dot_1097287358": {
|
||||||
"string": "Are you sure you want to cancel fulfillment? Canceling a fulfillment will restock products at a selected warehouse."
|
"string": "Are you sure you want to cancel fulfillment? Canceling a fulfillment will restock products at a selected warehouse."
|
||||||
},
|
},
|
||||||
|
@ -3309,71 +3441,10 @@
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Add Tracking Code"
|
"string": "Add Tracking Code"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_1119771899": {
|
|
||||||
"context": "fulfillment group tracking number",
|
|
||||||
"string": "Add tracking"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_1134347598": {
|
|
||||||
"context": "product price",
|
|
||||||
"string": "Price"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_1895667608": {
|
|
||||||
"context": "product name",
|
|
||||||
"string": "Product"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_2211099657": {
|
|
||||||
"context": "fulfillment group tracking number",
|
|
||||||
"string": "Edit tracking"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_2567258278": {
|
|
||||||
"context": "refunded fulfillment, section header",
|
|
||||||
"string": "Refunded ({quantity})"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_2796503714": {
|
|
||||||
"context": "ordered product quantity",
|
|
||||||
"string": "Quantity"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_3254150098": {
|
|
||||||
"string": "Tracking Number: {trackingNumber}"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_3494686506": {
|
|
||||||
"context": "section header",
|
|
||||||
"string": "Fulfilled ({quantity})"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_4039425374": {
|
|
||||||
"context": "cancelled fulfillment, section header",
|
|
||||||
"string": "Cancelled ({quantity})"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_693960049": {
|
|
||||||
"context": "ordered product sku",
|
|
||||||
"string": "SKU"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_732594284": {
|
|
||||||
"context": "button",
|
|
||||||
"string": "Cancel Fulfillment"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_77179533": {
|
|
||||||
"context": "fulfillment group",
|
|
||||||
"string": "Fulfilled from: {warehouseName}"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderFulfillment_dot_878013594": {
|
|
||||||
"context": "order line total price",
|
|
||||||
"string": "Total"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1154330234": {
|
|
||||||
"context": "transaction reference",
|
|
||||||
"string": "Transaction Reference {transactionReference}"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1230178536": {
|
"src_dot_orders_dot_components_dot_OrderHistory_dot_1230178536": {
|
||||||
"context": "order history message",
|
"context": "order history message",
|
||||||
"string": "Order address was updated"
|
"string": "Order address was updated"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_123236698": {
|
|
||||||
"string": "Shipment was refunded"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1322321687": {
|
|
||||||
"string": "Refunded amount"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_1463685940": {
|
"src_dot_orders_dot_components_dot_OrderHistory_dot_1463685940": {
|
||||||
"context": "order history message",
|
"context": "order history message",
|
||||||
"string": "Order was marked as paid"
|
"string": "Order was marked as paid"
|
||||||
|
@ -3489,9 +3560,6 @@
|
||||||
"context": "order history message",
|
"context": "order history message",
|
||||||
"string": "Payment failed"
|
"string": "Payment failed"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_492197448": {
|
|
||||||
"string": "Products refunded"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderHistory_dot_493321552": {
|
"src_dot_orders_dot_components_dot_OrderHistory_dot_493321552": {
|
||||||
"context": "order history message",
|
"context": "order history message",
|
||||||
"string": "Order cancel information was sent to customer"
|
"string": "Order cancel information was sent to customer"
|
||||||
|
@ -3512,6 +3580,14 @@
|
||||||
"context": "order history message",
|
"context": "order history message",
|
||||||
"string": "Order was cancelled"
|
"string": "Order was cancelled"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderHistory_dot_description": {
|
||||||
|
"context": "replacement created order history message description",
|
||||||
|
"string": "was created for replaced products"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderHistory_dot_draftNumber": {
|
||||||
|
"context": "replacement created order history message draft number",
|
||||||
|
"string": "Draft #{orderNumber} "
|
||||||
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderInvoiceEmailSendDialog_dot_1821123638": {
|
"src_dot_orders_dot_components_dot_OrderInvoiceEmailSendDialog_dot_1821123638": {
|
||||||
"string": "Are you sure you want to send this invoice: {invoiceNumber} to the customer?"
|
"string": "Are you sure you want to send this invoice: {invoiceNumber} to the customer?"
|
||||||
},
|
},
|
||||||
|
@ -3695,71 +3771,29 @@
|
||||||
"src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_353369701": {
|
"src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_353369701": {
|
||||||
"string": "No products matching given query"
|
"string": "No products matching given query"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_1580639738": {
|
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_1134347598": {
|
||||||
"context": "order refund amount",
|
"context": "product price",
|
||||||
"string": "Proposed refund amount"
|
"string": "Price"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_1705174606": {
|
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_1895667608": {
|
||||||
"context": "order refund amount",
|
"context": "product name",
|
||||||
"string": "Max Refund"
|
"string": "Product"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_1734445951": {
|
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_2796503714": {
|
||||||
"context": "order refund amount",
|
"context": "ordered product quantity",
|
||||||
"string": "Refund total amount"
|
"string": "Quantity"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_2045860028": {
|
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_693960049": {
|
||||||
"context": "order refund amount",
|
"context": "ordered product sku",
|
||||||
"string": "Authorized Amount"
|
"string": "SKU"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_2854815744": {
|
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_878013594": {
|
||||||
"context": "order refund amount",
|
"context": "order line total price",
|
||||||
"string": "Previously refunded"
|
"string": "Total"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_2907874606": {
|
"src_dot_orders_dot_components_dot_OrderRefundFulfilledProducts_dot_1097582574": {
|
||||||
"context": "order refund amount",
|
"context": "section header returned",
|
||||||
"string": "Selected products value"
|
"string": "Fulfillment returned"
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmountValues_dot_79173946": {
|
|
||||||
"context": "order refund amount",
|
|
||||||
"string": "Shipment cost"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_120912052": {
|
|
||||||
"context": "order refund amount",
|
|
||||||
"string": "Refunded items can’t be fulfilled"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_159210811": {
|
|
||||||
"string": "Amount must be bigger than 0"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_2256869831": {
|
|
||||||
"context": "section header",
|
|
||||||
"string": "Refunded Amount"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_2845258362": {
|
|
||||||
"context": "order refund amount, input button",
|
|
||||||
"string": "Refund"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_4033685232": {
|
|
||||||
"string": "Amount cannot be bigger than max refund"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_40513382": {
|
|
||||||
"context": "order refund amount, input button",
|
|
||||||
"string": "Refund {currency} {amount}"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_4224226791": {
|
|
||||||
"context": "label",
|
|
||||||
"string": "Automatic Amount"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_508357513": {
|
|
||||||
"context": "label",
|
|
||||||
"string": "Manual Amount"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_553737700": {
|
|
||||||
"context": "checkbox",
|
|
||||||
"string": "Refund shipment costs"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundAmount_dot_75546233": {
|
|
||||||
"context": "order refund amount, input label",
|
|
||||||
"string": "Amount"
|
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundFulfilledProducts_dot_1134347598": {
|
"src_dot_orders_dot_components_dot_OrderRefundFulfilledProducts_dot_1134347598": {
|
||||||
"context": "tabel column header",
|
"context": "tabel column header",
|
||||||
|
@ -3804,6 +3838,90 @@
|
||||||
"context": "page header with order number",
|
"context": "page header with order number",
|
||||||
"string": "Order #{orderNumber}"
|
"string": "Order #{orderNumber}"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_2256869831": {
|
||||||
|
"context": "section header",
|
||||||
|
"string": "Refunded Amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_40513382": {
|
||||||
|
"context": "order refund amount, input button",
|
||||||
|
"string": "Refund {currency} {amount}"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_4224226791": {
|
||||||
|
"context": "label",
|
||||||
|
"string": "Automatic Amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_508357513": {
|
||||||
|
"context": "label",
|
||||||
|
"string": "Manual Amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_553737700": {
|
||||||
|
"context": "checkbox",
|
||||||
|
"string": "Refund shipment costs"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_982301568": {
|
||||||
|
"context": "label",
|
||||||
|
"string": "No refund"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_amountTooBig": {
|
||||||
|
"context": "Amount error message",
|
||||||
|
"string": "Amount cannot be bigger than max refund"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_amountTooSmall": {
|
||||||
|
"context": "Amount error message",
|
||||||
|
"string": "Amount must be bigger than 0"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_authorizedAmount": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Authorized Amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_label": {
|
||||||
|
"context": "order refund amount, input label",
|
||||||
|
"string": "Amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_maxRefund": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Max Refund"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_previouslyRefunded": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Previously refunded"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_proposedRefundAmount": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Proposed refund amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_refundButton": {
|
||||||
|
"context": "order refund amount button",
|
||||||
|
"string": "Refund"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_refundCannotBeFulfilled": {
|
||||||
|
"context": "order refund subtitle",
|
||||||
|
"string": "Refunded items can't be fulfilled"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_refundTotalAmount": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Refund total amount"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_replacedProductsValue": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Replaced Products Value"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_returnButton": {
|
||||||
|
"context": "order return amount button",
|
||||||
|
"string": "Return & Replace products"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_returnCannotBeFulfilled": {
|
||||||
|
"context": "order return subtitle",
|
||||||
|
"string": "Returned items can't be fulfilled"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_selectedProductsValue": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Selected Products Value"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderRefundReturnAmount_dot_shipmentCost": {
|
||||||
|
"context": "order refund amount",
|
||||||
|
"string": "Shipment Cost"
|
||||||
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderRefundUnfulfilledProducts_dot_1134347598": {
|
"src_dot_orders_dot_components_dot_OrderRefundUnfulfilledProducts_dot_1134347598": {
|
||||||
"context": "tabel column header",
|
"context": "tabel column header",
|
||||||
"string": "Price"
|
"string": "Price"
|
||||||
|
@ -3851,6 +3969,82 @@
|
||||||
"context": "refund type",
|
"context": "refund type",
|
||||||
"string": "Refund Products"
|
"string": "Refund Products"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_1134347598": {
|
||||||
|
"context": "table column header",
|
||||||
|
"string": "Price"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_1784788864": {
|
||||||
|
"context": "table column header",
|
||||||
|
"string": "Return"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_1895667608": {
|
||||||
|
"context": "table column header",
|
||||||
|
"string": "Product"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_2049070632": {
|
||||||
|
"context": "table column header",
|
||||||
|
"string": "Replace"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_3988345170": {
|
||||||
|
"context": "button",
|
||||||
|
"string": "Set maximal quantities"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_cancelled": {
|
||||||
|
"context": "cancelled fulfillment, section header",
|
||||||
|
"string": "Cancelled ({quantity})"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_description": {
|
||||||
|
"context": "product no longer exists error description",
|
||||||
|
"string": "This product is no longer in database so it can’t be replaced, nor returned"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_fulfilled": {
|
||||||
|
"context": "section header",
|
||||||
|
"string": "Fulfilled ({quantity})"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_improperValue": {
|
||||||
|
"context": "error message",
|
||||||
|
"string": "Improper value"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_refunded": {
|
||||||
|
"context": "refunded fulfillment, section header",
|
||||||
|
"string": "Refunded ({quantity})"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_refundedAndReturned": {
|
||||||
|
"context": "cancelled fulfillment, section header",
|
||||||
|
"string": "Refunded and Returned ({quantity})"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_replaced": {
|
||||||
|
"context": "refunded fulfillment, section header",
|
||||||
|
"string": "Replaced ({quantity})"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_returned": {
|
||||||
|
"context": "refunded fulfillment, section header",
|
||||||
|
"string": "Returned ({quantity})"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_title": {
|
||||||
|
"context": "product no longer exists error title",
|
||||||
|
"string": "Product no longer exists"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_titleFulfilled": {
|
||||||
|
"context": "section header",
|
||||||
|
"string": "Fulfillment - #{fulfilmentId}"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_titleUnfulfilled": {
|
||||||
|
"context": "section header",
|
||||||
|
"string": "Unfulfilled Items"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_OrderReturnRefundItemsCard_dot_unfulfilled": {
|
||||||
|
"context": "section header",
|
||||||
|
"string": "Unfulfilled"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_appTitle": {
|
||||||
|
"context": "page header with order number",
|
||||||
|
"string": "Order #{orderNumber}"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_components_dot_OrderReturnPage_dot_pageTitle": {
|
||||||
|
"context": "page header",
|
||||||
|
"string": "Order no. {orderNumber} - Replace/Return"
|
||||||
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderSettingsPage_dot_1149215359": {
|
"src_dot_orders_dot_components_dot_OrderSettingsPage_dot_1149215359": {
|
||||||
"context": "header",
|
"context": "header",
|
||||||
"string": "Order settings"
|
"string": "Order settings"
|
||||||
|
@ -3874,34 +4068,10 @@
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Edit Shipping Method"
|
"string": "Edit Shipping Method"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_1134347598": {
|
"src_dot_orders_dot_components_dot_OrderUnfulfilledProductsCard_dot_2095687440": {
|
||||||
"context": "product unit price",
|
|
||||||
"string": "Price"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_1895667608": {
|
|
||||||
"context": "product name",
|
|
||||||
"string": "Product"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_2095687440": {
|
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Fulfill"
|
"string": "Fulfill"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_2796503714": {
|
|
||||||
"context": "ordered products",
|
|
||||||
"string": "Quantity"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_2886647373": {
|
|
||||||
"context": "section header",
|
|
||||||
"string": "Unfulfilled ({quantity})"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_693960049": {
|
|
||||||
"context": "ordered product sku",
|
|
||||||
"string": "SKU"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_components_dot_OrderUnfulfilledItems_dot_878013594": {
|
|
||||||
"context": "order line total price",
|
|
||||||
"string": "Total"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_1039259580": {
|
"src_dot_orders_dot_views_dot_OrderDetails_dot_1039259580": {
|
||||||
"string": "We’re generating the invoice you requested. Please wait a couple of moments"
|
"string": "We’re generating the invoice you requested. Please wait a couple of moments"
|
||||||
},
|
},
|
||||||
|
@ -3991,6 +4161,18 @@
|
||||||
"context": "order refunded success message",
|
"context": "order refunded success message",
|
||||||
"string": "Refunded Items"
|
"string": "Refunded Items"
|
||||||
},
|
},
|
||||||
|
"src_dot_orders_dot_views_dot_OrderReturn_dot_cannotRefundDescription": {
|
||||||
|
"context": "order return error description when cannot refund",
|
||||||
|
"string": "We’ve encountered a problem while refunding the products. Product’s were not refunded. Please try again."
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_views_dot_OrderReturn_dot_cannotRefundTitle": {
|
||||||
|
"context": "order return error title when cannot refund",
|
||||||
|
"string": "Couldn't refund products"
|
||||||
|
},
|
||||||
|
"src_dot_orders_dot_views_dot_OrderReturn_dot_successAlert": {
|
||||||
|
"context": "order returned success message",
|
||||||
|
"string": "Successfully returned products!"
|
||||||
|
},
|
||||||
"src_dot_pageTypes": {
|
"src_dot_pageTypes": {
|
||||||
"context": "page types section name",
|
"context": "page types section name",
|
||||||
"string": "Page Types"
|
"string": "Page Types"
|
||||||
|
@ -4237,6 +4419,10 @@
|
||||||
"context": "payment status",
|
"context": "payment status",
|
||||||
"string": "Partially refunded"
|
"string": "Partially refunded"
|
||||||
},
|
},
|
||||||
|
"src_dot_partiallyReturned": {
|
||||||
|
"context": "order status",
|
||||||
|
"string": "Partially returned"
|
||||||
|
},
|
||||||
"src_dot_permissionGroups": {
|
"src_dot_permissionGroups": {
|
||||||
"context": "permission groups section name",
|
"context": "permission groups section name",
|
||||||
"string": "Permission Groups"
|
"string": "Permission Groups"
|
||||||
|
@ -5272,6 +5458,10 @@
|
||||||
"src_dot_requiredField": {
|
"src_dot_requiredField": {
|
||||||
"string": "This field is required"
|
"string": "This field is required"
|
||||||
},
|
},
|
||||||
|
"src_dot_returned": {
|
||||||
|
"context": "order status",
|
||||||
|
"string": "Returned"
|
||||||
|
},
|
||||||
"src_dot_sales": {
|
"src_dot_sales": {
|
||||||
"context": "sales section name",
|
"context": "sales section name",
|
||||||
"string": "Sales"
|
"string": "Sales"
|
||||||
|
@ -6251,6 +6441,9 @@
|
||||||
"src_dot_translations_dot_components_dot_TranslationsVouchersPage_dot_2599922713": {
|
"src_dot_translations_dot_components_dot_TranslationsVouchersPage_dot_2599922713": {
|
||||||
"string": "Voucher Name"
|
"string": "Voucher Name"
|
||||||
},
|
},
|
||||||
|
"src_dot_unauthorizedDashboardAccess": {
|
||||||
|
"string": "Only staff users can access the dashboard"
|
||||||
|
},
|
||||||
"src_dot_unconfirmed": {
|
"src_dot_unconfirmed": {
|
||||||
"context": "order status",
|
"context": "order status",
|
||||||
"string": "Unconfirmed"
|
"string": "Unconfirmed"
|
||||||
|
@ -6707,6 +6900,10 @@
|
||||||
"context": "table column header",
|
"context": "table column header",
|
||||||
"string": "Quantity"
|
"string": "Quantity"
|
||||||
},
|
},
|
||||||
|
"transaction reference subtitle": {
|
||||||
|
"context": "transaction reference subtitle",
|
||||||
|
"string": "Transaction reference"
|
||||||
|
},
|
||||||
"voucherDetailsUnassignCategory": {
|
"voucherDetailsUnassignCategory": {
|
||||||
"context": "unassign category from voucher, button",
|
"context": "unassign category from voucher, button",
|
||||||
"string": "Unassign"
|
"string": "Unassign"
|
||||||
|
|
132
package-lock.json
generated
132
package-lock.json
generated
|
@ -30069,9 +30069,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@editorjs/editorjs": {
|
"@editorjs/editorjs": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.19.1.tgz",
|
||||||
"integrity": "sha512-8PUVaBZx69IrG8dNrE+FZbHSiRTR8ql8L/cmEi1mOdEdTqnOLq5Wv9dgemK00mBWEgNoavMAjtGQpItGknAa8A==",
|
"integrity": "sha512-5lN7r5B2NCE8VJdsS3poX3Qg9rNwzpxZ+6Jjif3hAVZTYpQwg5wXEpAHFNbuavS0T5Ji+0ID31DQFotVI4PosA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"codex-notifier": "^1.1.2",
|
"codex-notifier": "^1.1.2",
|
||||||
"codex-tooltip": "^1.0.1"
|
"codex-tooltip": "^1.0.1"
|
||||||
|
@ -31110,6 +31110,120 @@
|
||||||
"any-observable": "^0.3.0"
|
"any-observable": "^0.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@sentry/browser": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-R4+MHb5FyVZCz3EVnaquvT1mwOM2MWP4gBqjYEADY5m0XWoHiJf0skFkWt8iEKJanzGbhl4PMb9gHuJj6YfVLw==",
|
||||||
|
"requires": {
|
||||||
|
"@sentry/core": "6.0.0",
|
||||||
|
"@sentry/types": "6.0.0",
|
||||||
|
"@sentry/utils": "6.0.0",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/cli": {
|
||||||
|
"version": "1.61.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.61.0.tgz",
|
||||||
|
"integrity": "sha512-pHEhqP1bB4sdO7N5ow/IkRBrPbKT9HZRinq4PhTVIvmG+NW4VVuVZ6k4tlbp+JXmzMcUc/iXynVkTL7zJIlTQw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"https-proxy-agent": "^5.0.0",
|
||||||
|
"mkdirp": "^0.5.5",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
|
"progress": "^2.0.3",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"https-proxy-agent": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/core": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-afAiOachs/WfGWc9LsJBFnJMhqQVENyzfSMnf7sLRvxPAw8n7IrXY0R09MKmG0SlAnTKN2pWoQFzFF+J3NuHBA==",
|
||||||
|
"requires": {
|
||||||
|
"@sentry/hub": "6.0.0",
|
||||||
|
"@sentry/minimal": "6.0.0",
|
||||||
|
"@sentry/types": "6.0.0",
|
||||||
|
"@sentry/utils": "6.0.0",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/hub": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-s8IsW6LvEH7ACnniQcxxb/9uEyjmoQ/TAoryTJN2qyPzzrHTw8NCyMuJvK+8ivUvRViz5AvtuOFf8AJlh9lzeA==",
|
||||||
|
"requires": {
|
||||||
|
"@sentry/types": "6.0.0",
|
||||||
|
"@sentry/utils": "6.0.0",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/minimal": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-daYdEzTr+ERMwViu6RpWHOfk0oZrSNqdx+7bejTqmFHqO4pt+9ZrMiw3vinL+MWQcKXwD95uXBz6O/ryrVdPtg==",
|
||||||
|
"requires": {
|
||||||
|
"@sentry/hub": "6.0.0",
|
||||||
|
"@sentry/types": "6.0.0",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/react": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-GYX110NSodd8wGUbnyxemndTijM+U7dI/WjFSPOyJdLB2hzzPjJ9kUqtuobT/JlGzbWE2278WysAuySne6bUGw==",
|
||||||
|
"requires": {
|
||||||
|
"@sentry/browser": "6.0.0",
|
||||||
|
"@sentry/minimal": "6.0.0",
|
||||||
|
"@sentry/types": "6.0.0",
|
||||||
|
"@sentry/utils": "6.0.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/types": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-yueRSRGPCahuju/UMdtOt8LIIncbpwLINQd9Q8E4OXtoPpMHR6Oun8sMKCPd+Wq3piI5yRDzKkGCl+sH7mHVrA=="
|
||||||
|
},
|
||||||
|
"@sentry/utils": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-dMMWOT69bQ4CF1R33dOnXIOyiHRWsUAON3nFVljV1JNNTDA69YwaF9f5FIT0DKpO4qhgTlElsm8WgHI9prAVEQ==",
|
||||||
|
"requires": {
|
||||||
|
"@sentry/types": "6.0.0",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@sentry/webpack-plugin": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-1cS99mnHqASYtMlHi2J107p6x3lfC5NmLOgA0iI6avaaFes8RTQMlW8YT2CyrvhtQod1bViPZOlh3NOVC8vnOA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@sentry/cli": "^1.58.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@sindresorhus/fnv1a": {
|
"@sindresorhus/fnv1a": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-1.2.0.tgz",
|
||||||
|
@ -36245,9 +36359,9 @@
|
||||||
"integrity": "sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg=="
|
"integrity": "sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg=="
|
||||||
},
|
},
|
||||||
"codex-tooltip": {
|
"codex-tooltip": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.2.tgz",
|
||||||
"integrity": "sha512-1xLb1NZbxguNtf02xBRhDphq/EXvMMeEbY0ievjQTHqf8UjXsD41evGk9rqcbjpl+JOjNgtwnp1OaU/X/h6fhQ=="
|
"integrity": "sha512-oC+Bu5X/zyhbPydgMSLWKoM/+vkJMqaLWu3Dt/jZgXS3MWK23INwC5DMBrVXZSufAFk0i0SUni38k9rLMyZn/w=="
|
||||||
},
|
},
|
||||||
"collapse-white-space": {
|
"collapse-white-space": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
|
@ -46708,6 +46822,12 @@
|
||||||
"ipaddr.js": "1.9.0"
|
"ipaddr.js": "1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"prr": {
|
"prr": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"npm": ">=6.11.0"
|
"npm": ">=6.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@editorjs/editorjs": "^2.19.0",
|
"@editorjs/editorjs": "^2.19.1",
|
||||||
"@editorjs/header": "^2.6.1",
|
"@editorjs/header": "^2.6.1",
|
||||||
"@editorjs/image": "^2.6.0",
|
"@editorjs/image": "^2.6.0",
|
||||||
"@editorjs/list": "^1.6.1",
|
"@editorjs/list": "^1.6.1",
|
||||||
|
@ -27,6 +27,7 @@
|
||||||
"@material-ui/styles": "^4.5.2",
|
"@material-ui/styles": "^4.5.2",
|
||||||
"@saleor/macaw-ui": "^0.1.1-9",
|
"@saleor/macaw-ui": "^0.1.1-9",
|
||||||
"@types/faker": "^5.1.6",
|
"@types/faker": "^5.1.6",
|
||||||
|
"@sentry/react": "^6.0.0",
|
||||||
"apollo": "^2.21.2",
|
"apollo": "^2.21.2",
|
||||||
"apollo-cache-inmemory": "^1.6.5",
|
"apollo-cache-inmemory": "^1.6.5",
|
||||||
"apollo-client": "^2.6.8",
|
"apollo-client": "^2.6.8",
|
||||||
|
@ -94,6 +95,7 @@
|
||||||
"@pollyjs/adapter-node-http": "^5.0.0",
|
"@pollyjs/adapter-node-http": "^5.0.0",
|
||||||
"@pollyjs/core": "^5.0.0",
|
"@pollyjs/core": "^5.0.0",
|
||||||
"@pollyjs/persister-fs": "^5.0.0",
|
"@pollyjs/persister-fs": "^5.0.0",
|
||||||
|
"@sentry/webpack-plugin": "^1.14.0",
|
||||||
"@storybook/addon-storyshots": "^5.2.8",
|
"@storybook/addon-storyshots": "^5.2.8",
|
||||||
"@storybook/react": "^5.1.9",
|
"@storybook/react": "^5.1.9",
|
||||||
"@testing-library/react-hooks": "^1.1.0",
|
"@testing-library/react-hooks": "^1.1.0",
|
||||||
|
@ -228,6 +230,7 @@
|
||||||
"test:e2e:dev": "start-server-and-test start http://localhost:9000 cy:open",
|
"test:e2e:dev": "start-server-and-test start http://localhost:9000 cy:open",
|
||||||
"test": "jest src/",
|
"test": "jest src/",
|
||||||
"transpile-messages": "node scripts/transpile-tx.js",
|
"transpile-messages": "node scripts/transpile-tx.js",
|
||||||
"lint": "npx eslint \"src/**/*.@(tsx|ts|jsx|js)\" --fix ; npx prettier --check \"src/**/*.@(tsx|ts|jsx|js)\" --write"
|
"lint": "npx eslint \"src/**/*.@(tsx|ts|jsx|js)\" --fix ; npx prettier --check \"src/**/*.@(tsx|ts|jsx|js)\" --write",
|
||||||
|
"postbuild": "rimraf ./build/**/*.js.map"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
},
|
},
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"_id": "f515e15cbc83df73e5bd41437971c2e6",
|
"_id": "a3088678db2635ada66ab049f76c9722",
|
||||||
"_order": 0,
|
"_order": 0,
|
||||||
"cache": {},
|
"cache": {},
|
||||||
"request": {
|
"request": {
|
||||||
"bodySize": 691,
|
"bodySize": 702,
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "691"
|
"value": "702"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
|
@ -56,61 +56,53 @@
|
||||||
"postData": {
|
"postData": {
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"params": [],
|
"params": [],
|
||||||
"text": "[{\"operationName\":\"VerifyToken\",\"variables\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5NjAyODUxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZX0.eo8_Ew98HICB4cFQN2U7mCJ8ydGVOvQLGRT4CnkufMc\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
"text": "[{\"operationName\":\"VerifyToken\",\"variables\":{\"token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMDYzMjg0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU5BPT0iLCJpc19zdGFmZiI6dHJ1ZX0.QDp4vlm1tKhk8iFnY2MnREvO-IubI5j8g_Wylb1XJqc\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
},
|
},
|
||||||
"queryString": [],
|
"queryString": [],
|
||||||
"url": "http://localhost:8000/graphql/"
|
"url": "http://localhost:8000/graphql/"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"bodySize": 1619,
|
"bodySize": 1765,
|
||||||
"content": {
|
"content": {
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"size": 1619,
|
"size": 1765,
|
||||||
"text": "[{\"data\": {\"tokenVerify\": {\"payload\": {\"iat\": 1596028218, \"exp\": 1596028518, \"token\": \"C3Sk2kLRVuPA\", \"email\": \"admin@example.com\", \"type\": \"access\", \"user_id\": \"VXNlcjoyMQ==\", \"is_staff\": true}, \"user\": {\"id\": \"VXNlcjoyMQ==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"userPermissions\": [{\"code\": \"MANAGE_APPS\", \"name\": \"Manage apps\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_CHECKOUTS\", \"name\": \"Manage checkouts\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_DISCOUNTS\", \"name\": \"Manage sales and vouchers.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_GIFT_CARD\", \"name\": \"Manage gift cards.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_MENUS\", \"name\": \"Manage navigation.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_ORDERS\", \"name\": \"Manage orders.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PAGES\", \"name\": \"Manage pages.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PLUGINS\", \"name\": \"Manage plugins\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCTS\", \"name\": \"Manage products.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SETTINGS\", \"name\": \"Manage settings.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SHIPPING\", \"name\": \"Manage shipping.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_STAFF\", \"name\": \"Manage staff.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_TRANSLATIONS\", \"name\": \"Manage translations.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_USERS\", \"name\": \"Manage customers.\", \"__typename\": \"UserPermission\"}], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"VerifyToken\"}}}]"
|
"text": "[{\"data\": {\"tokenVerify\": {\"payload\": {\"iat\": 1610632544, \"exp\": 1610632844, \"token\": \"ksEVMvgg6Bfi\", \"email\": \"admin@example.com\", \"type\": \"access\", \"user_id\": \"VXNlcjoyNA==\", \"is_staff\": true}, \"user\": {\"id\": \"VXNlcjoyNA==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": true, \"userPermissions\": [{\"code\": \"MANAGE_APPS\", \"name\": \"Manage apps\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_CHECKOUTS\", \"name\": \"Manage checkouts\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_DISCOUNTS\", \"name\": \"Manage sales and vouchers.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_GIFT_CARD\", \"name\": \"Manage gift cards.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_MENUS\", \"name\": \"Manage navigation.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_ORDERS\", \"name\": \"Manage orders.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PAGES\", \"name\": \"Manage pages.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PLUGINS\", \"name\": \"Manage plugins\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES\", \"name\": \"Manage product types and attributes.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCTS\", \"name\": \"Manage products.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SETTINGS\", \"name\": \"Manage settings.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SHIPPING\", \"name\": \"Manage shipping.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_STAFF\", \"name\": \"Manage staff.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_TRANSLATIONS\", \"name\": \"Manage translations.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_USERS\", \"name\": \"Manage customers.\", \"__typename\": \"UserPermission\"}], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"VerifyToken\"}}}]"
|
||||||
},
|
},
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
"value": "Thu, 14 Jan 2021 14:10:40 GMT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"value": "WSGIServer/0.2 CPython/3.8.1"
|
"value": "WSGIServer/0.2 CPython/3.8.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content-type",
|
"name": "content-type",
|
||||||
"value": "application/json"
|
"value": "application/json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "access-control-allow-origin",
|
|
||||||
"value": "http://localhost:9000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-methods",
|
|
||||||
"value": "POST, OPTIONS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-headers",
|
|
||||||
"value": "Origin, Content-Type, Accept, Authorization"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "1619"
|
"value": "1765"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "x-content-type-options",
|
"name": "x-content-type-options",
|
||||||
"value": "nosniff"
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "same-origin"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"headersSize": 336,
|
"headersSize": 194,
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"redirectURL": "",
|
"redirectURL": "",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"statusText": "OK"
|
"statusText": "OK"
|
||||||
},
|
},
|
||||||
"startedDateTime": "2020-07-29T13:10:18.327Z",
|
"startedDateTime": "2021-01-14T14:10:40.434Z",
|
||||||
"time": 23,
|
"time": 155,
|
||||||
"timings": {
|
"timings": {
|
||||||
"blocked": -1,
|
"blocked": -1,
|
||||||
"connect": -1,
|
"connect": -1,
|
||||||
|
@ -118,7 +110,7 @@
|
||||||
"receive": 0,
|
"receive": 0,
|
||||||
"send": 0,
|
"send": 0,
|
||||||
"ssl": -1,
|
"ssl": -1,
|
||||||
"wait": 23
|
"wait": 155
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
},
|
},
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"_id": "7c460842cac4a92c188d5451dfc533a2",
|
"_id": "a2b8a02f624e52cd2b73a831f65d9a52",
|
||||||
"_order": 0,
|
"_order": 0,
|
||||||
"cache": {},
|
"cache": {},
|
||||||
"request": {
|
"request": {
|
||||||
"bodySize": 587,
|
"bodySize": 598,
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "587"
|
"value": "598"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
|
@ -56,74 +56,65 @@
|
||||||
"postData": {
|
"postData": {
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"params": [],
|
"params": [],
|
||||||
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"admin\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"admin\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
},
|
},
|
||||||
"queryString": [],
|
"queryString": [],
|
||||||
"url": "http://localhost:8000/graphql/"
|
"url": "http://localhost:8000/graphql/"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"bodySize": 1830,
|
"bodySize": 1976,
|
||||||
"content": {
|
"content": {
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"size": 1830,
|
"size": 1976,
|
||||||
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"rLPNMGNYKXH8VY4UNEWl4nEOFMseocljioigPl36IM2CqbdmOTEpNwvdHBAJ1ZWQ\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5NjAyODUxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU1RPT0iLCJpc19zdGFmZiI6dHJ1ZX0.eo8_Ew98HICB4cFQN2U7mCJ8ydGVOvQLGRT4CnkufMc\", \"user\": {\"id\": \"VXNlcjoyMQ==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"userPermissions\": [{\"code\": \"MANAGE_APPS\", \"name\": \"Manage apps\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_CHECKOUTS\", \"name\": \"Manage checkouts\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_DISCOUNTS\", \"name\": \"Manage sales and vouchers.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_GIFT_CARD\", \"name\": \"Manage gift cards.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_MENUS\", \"name\": \"Manage navigation.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_ORDERS\", \"name\": \"Manage orders.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PAGES\", \"name\": \"Manage pages.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PLUGINS\", \"name\": \"Manage plugins\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCTS\", \"name\": \"Manage products.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SETTINGS\", \"name\": \"Manage settings.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SHIPPING\", \"name\": \"Manage shipping.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_STAFF\", \"name\": \"Manage staff.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_TRANSLATIONS\", \"name\": \"Manage translations.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_USERS\", \"name\": \"Manage customers.\", \"__typename\": \"UserPermission\"}], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]"
|
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"UIzzJSFalS8pplfM1j5QNIUNiXb0VFH3kbe6kfTddYvLjJ9DhMasCtHJKXoGDfbw\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMDYzMjg0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6ImFjY2VzcyIsInVzZXJfaWQiOiJWWE5sY2pveU5BPT0iLCJpc19zdGFmZiI6dHJ1ZX0.QDp4vlm1tKhk8iFnY2MnREvO-IubI5j8g_Wylb1XJqc\", \"user\": {\"id\": \"VXNlcjoyNA==\", \"email\": \"admin@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": true, \"userPermissions\": [{\"code\": \"MANAGE_APPS\", \"name\": \"Manage apps\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_CHECKOUTS\", \"name\": \"Manage checkouts\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_DISCOUNTS\", \"name\": \"Manage sales and vouchers.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_GIFT_CARD\", \"name\": \"Manage gift cards.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_MENUS\", \"name\": \"Manage navigation.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_ORDERS\", \"name\": \"Manage orders.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PAGES\", \"name\": \"Manage pages.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PLUGINS\", \"name\": \"Manage plugins\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCT_TYPES_AND_ATTRIBUTES\", \"name\": \"Manage product types and attributes.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_PRODUCTS\", \"name\": \"Manage products.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SETTINGS\", \"name\": \"Manage settings.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_SHIPPING\", \"name\": \"Manage shipping.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_STAFF\", \"name\": \"Manage staff.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_TRANSLATIONS\", \"name\": \"Manage translations.\", \"__typename\": \"UserPermission\"}, {\"code\": \"MANAGE_USERS\", \"name\": \"Manage customers.\", \"__typename\": \"UserPermission\"}], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]"
|
||||||
},
|
},
|
||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"name": "refreshToken",
|
"name": "refreshToken",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"secure": true,
|
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMzIyNDU0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lOQT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6IlVJenpKU0ZhbFM4cHBsZk0xajVRTklVTmlYYjBWRkgza2JlNmtmVGRkWXZMako5RGhNYXNDdEhKS1hvR0RmYncifQ.Br0GWGPPcnysyUxukjBBfXNbwCAm2qlR5OYClwFF3ZQ"
|
||||||
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5ODYyMDIxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lNUT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6InJMUE5NR05ZS1hIOFZZNFVORVdsNG5FT0ZNc2VvY2xqaW9pZ1BsMzZJTTJDcWJkbU9URXBOd3ZkSEJBSjFaV1EifQ.boD8G4pkSnZF-PLl5oOg85Uj-mqTiAzOkua9aAG3Bz4"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
"value": "Thu, 14 Jan 2021 13:55:44 GMT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"value": "WSGIServer/0.2 CPython/3.8.1"
|
"value": "WSGIServer/0.2 CPython/3.8.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content-type",
|
"name": "content-type",
|
||||||
"value": "application/json"
|
"value": "application/json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "access-control-allow-origin",
|
|
||||||
"value": "http://localhost:9000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-methods",
|
|
||||||
"value": "POST, OPTIONS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-headers",
|
|
||||||
"value": "Origin, Content-Type, Accept, Authorization"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "1830"
|
"value": "1976"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "x-content-type-options",
|
"name": "x-content-type-options",
|
||||||
"value": "nosniff"
|
"value": "nosniff"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "same-origin"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
"name": "set-cookie",
|
"name": "set-cookie",
|
||||||
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTYwMjgyMTgsImV4cCI6MTU5ODYyMDIxOCwidG9rZW4iOiJDM1NrMmtMUlZ1UEEiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lNUT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6InJMUE5NR05ZS1hIOFZZNFVORVdsNG5FT0ZNc2VvY2xqaW9pZ1BsMzZJTTJDcWJkbU9URXBOd3ZkSEJBSjFaV1EifQ.boD8G4pkSnZF-PLl5oOg85Uj-mqTiAzOkua9aAG3Bz4; HttpOnly; Path=/; Secure"
|
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2MzI1NDQsImV4cCI6MTYxMzIyNDU0NCwidG9rZW4iOiJrc0VWTXZnZzZCZmkiLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwidHlwZSI6InJlZnJlc2giLCJ1c2VyX2lkIjoiVlhObGNqb3lOQT09IiwiaXNfc3RhZmYiOnRydWUsImNzcmZUb2tlbiI6IlVJenpKU0ZhbFM4cHBsZk0xajVRTklVTmlYYjBWRkgza2JlNmtmVGRkWXZMako5RGhNYXNDdEhKS1hvR0RmYncifQ.Br0GWGPPcnysyUxukjBBfXNbwCAm2qlR5OYClwFF3ZQ; HttpOnly; Path=/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"headersSize": 768,
|
"headersSize": 618,
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"redirectURL": "",
|
"redirectURL": "",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"statusText": "OK"
|
"statusText": "OK"
|
||||||
},
|
},
|
||||||
"startedDateTime": "2020-07-29T13:10:18.064Z",
|
"startedDateTime": "2021-01-14T13:55:44.094Z",
|
||||||
"time": 118,
|
"time": 392,
|
||||||
"timings": {
|
"timings": {
|
||||||
"blocked": -1,
|
"blocked": -1,
|
||||||
"connect": -1,
|
"connect": -1,
|
||||||
|
@ -131,7 +122,7 @@
|
||||||
"receive": 0,
|
"receive": 0,
|
||||||
"send": 0,
|
"send": 0,
|
||||||
"ssl": -1,
|
"ssl": -1,
|
||||||
"wait": 118
|
"wait": 392
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
},
|
},
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"_id": "4836098613648775386c1e10728424dd",
|
"_id": "b1557b45bbbf7aed1a4a53f5141ca324",
|
||||||
"_order": 0,
|
"_order": 0,
|
||||||
"cache": {},
|
"cache": {},
|
||||||
"request": {
|
"request": {
|
||||||
"bodySize": 428,
|
"bodySize": 439,
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "428"
|
"value": "439"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"postData": {
|
"postData": {
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"params": [],
|
"params": [],
|
||||||
"text": "[{\"operationName\":\"VerifyToken\",\"variables\":{\"token\":\"NotAToken\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
"text": "[{\"operationName\":\"VerifyToken\",\"variables\":{\"token\":\"NotAToken\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation VerifyToken($token: String!) {\\n tokenVerify(token: $token) {\\n payload\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
},
|
},
|
||||||
"queryString": [],
|
"queryString": [],
|
||||||
"url": "http://localhost:8000/graphql/"
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
@ -72,28 +72,16 @@
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
"value": "Thu, 14 Jan 2021 14:10:40 GMT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"value": "WSGIServer/0.2 CPython/3.8.1"
|
"value": "WSGIServer/0.2 CPython/3.8.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content-type",
|
"name": "content-type",
|
||||||
"value": "application/json"
|
"value": "application/json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "access-control-allow-origin",
|
|
||||||
"value": "http://localhost:9000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-methods",
|
|
||||||
"value": "POST, OPTIONS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-headers",
|
|
||||||
"value": "Origin, Content-Type, Accept, Authorization"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "89"
|
"value": "89"
|
||||||
|
@ -101,16 +89,20 @@
|
||||||
{
|
{
|
||||||
"name": "x-content-type-options",
|
"name": "x-content-type-options",
|
||||||
"value": "nosniff"
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "same-origin"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"headersSize": 334,
|
"headersSize": 192,
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"redirectURL": "",
|
"redirectURL": "",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"statusText": "OK"
|
"statusText": "OK"
|
||||||
},
|
},
|
||||||
"startedDateTime": "2020-07-29T13:10:18.368Z",
|
"startedDateTime": "2021-01-14T14:10:40.611Z",
|
||||||
"time": 6,
|
"time": 25,
|
||||||
"timings": {
|
"timings": {
|
||||||
"blocked": -1,
|
"blocked": -1,
|
||||||
"connect": -1,
|
"connect": -1,
|
||||||
|
@ -118,7 +110,7 @@
|
||||||
"receive": 0,
|
"receive": 0,
|
||||||
"send": 0,
|
"send": 0,
|
||||||
"ssl": -1,
|
"ssl": -1,
|
||||||
"wait": 6
|
"wait": 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will not be logged if is non-staff",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "5.0.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "0b09ec35ecae5b17a2ccda062b1d6ef5",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 602,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "602"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "user-agent",
|
||||||
|
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept-encoding",
|
||||||
|
"value": "gzip,deflate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "connection",
|
||||||
|
"value": "close"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "host",
|
||||||
|
"value": "localhost:8000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 254,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"method": "POST",
|
||||||
|
"postData": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"params": [],
|
||||||
|
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"client@example.com\",\"password\":\"password\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 616,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 616,
|
||||||
|
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"Gac5v8mZt6dW0HBXp5RNt8GAWciTbVzsycpqtUKV797npCXajke5h9VoF4l9MreP\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2OTg2MzMsImV4cCI6MTYxMDY5ODkzMywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJhY2Nlc3MiLCJ1c2VyX2lkIjoiVlhObGNqb3pNQT09IiwiaXNfc3RhZmYiOmZhbHNlfQ.RVYwqQSPEZoi2E_ImC30Ml37RJ2Fu6AnSmfDkAYMcqY\", \"user\": {\"id\": \"VXNlcjozMA==\", \"email\": \"client@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": false, \"userPermissions\": [], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"httpOnly": true,
|
||||||
|
"name": "refreshToken",
|
||||||
|
"path": "/",
|
||||||
|
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2OTg2MzMsImV4cCI6MTYxMzI5MDYzMywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoiR2FjNXY4bVp0NmRXMEhCWHA1Uk50OEdBV2NpVGJWenN5Y3BxdFVLVjc5N25wQ1hhamtlNWg5Vm9GNGw5TXJlUCJ9.jUF_9vvtwT8EUbQ4GM7u0YVivk7TiSoSecHDZ0jJ2MI"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Fri, 15 Jan 2021 08:17:13 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.8.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "616"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "same-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "set-cookie",
|
||||||
|
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2OTg2MzMsImV4cCI6MTYxMzI5MDYzMywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoiR2FjNXY4bVp0NmRXMEhCWHA1Uk50OEdBV2NpVGJWenN5Y3BxdFVLVjc5N25wQ1hhamtlNWg5Vm9GNGw5TXJlUCJ9.jUF_9vvtwT8EUbQ4GM7u0YVivk7TiSoSecHDZ0jJ2MI; HttpOnly; Path=/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 619,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2021-01-15T08:17:12.850Z",
|
||||||
|
"time": 623,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 623
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,11 @@
|
||||||
},
|
},
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"_id": "86487093ff8b070d496fcdc566e01adf",
|
"_id": "d94d7821dc951e48c410d691d7eccdef",
|
||||||
"_order": 0,
|
"_order": 0,
|
||||||
"cache": {},
|
"cache": {},
|
||||||
"request": {
|
"request": {
|
||||||
"bodySize": 603,
|
"bodySize": 614,
|
||||||
"cookies": [],
|
"cookies": [],
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "603"
|
"value": "614"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_fromType": "array",
|
"_fromType": "array",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"postData": {
|
"postData": {
|
||||||
"mimeType": "application/json",
|
"mimeType": "application/json",
|
||||||
"params": [],
|
"params": [],
|
||||||
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"NotAValidPassword123!\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"admin@example.com\",\"password\":\"NotAValidPassword123!\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
},
|
},
|
||||||
"queryString": [],
|
"queryString": [],
|
||||||
"url": "http://localhost:8000/graphql/"
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
@ -72,28 +72,16 @@
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"name": "date",
|
"name": "date",
|
||||||
"value": "Wed, 29 Jul 2020 13:10:18 GMT"
|
"value": "Thu, 14 Jan 2021 13:55:45 GMT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"value": "WSGIServer/0.2 CPython/3.8.1"
|
"value": "WSGIServer/0.2 CPython/3.8.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "content-type",
|
"name": "content-type",
|
||||||
"value": "application/json"
|
"value": "application/json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "access-control-allow-origin",
|
|
||||||
"value": "http://localhost:9000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-methods",
|
|
||||||
"value": "POST, OPTIONS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "access-control-allow-headers",
|
|
||||||
"value": "Origin, Content-Type, Accept, Authorization"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "content-length",
|
"name": "content-length",
|
||||||
"value": "214"
|
"value": "214"
|
||||||
|
@ -101,16 +89,20 @@
|
||||||
{
|
{
|
||||||
"name": "x-content-type-options",
|
"name": "x-content-type-options",
|
||||||
"value": "nosniff"
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "same-origin"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"headersSize": 335,
|
"headersSize": 193,
|
||||||
"httpVersion": "HTTP/1.1",
|
"httpVersion": "HTTP/1.1",
|
||||||
"redirectURL": "",
|
"redirectURL": "",
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"statusText": "OK"
|
"statusText": "OK"
|
||||||
},
|
},
|
||||||
"startedDateTime": "2020-07-29T13:10:18.208Z",
|
"startedDateTime": "2021-01-14T13:55:44.521Z",
|
||||||
"time": 99,
|
"time": 1183,
|
||||||
"timings": {
|
"timings": {
|
||||||
"blocked": -1,
|
"blocked": -1,
|
||||||
"connect": -1,
|
"connect": -1,
|
||||||
|
@ -118,7 +110,7 @@
|
||||||
"receive": 0,
|
"receive": 0,
|
||||||
"send": 0,
|
"send": 0,
|
||||||
"ssl": -1,
|
"ssl": -1,
|
||||||
"wait": 99
|
"wait": 1183
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"_recordingName": "User/will not be logged in if is non-staff",
|
||||||
|
"creator": {
|
||||||
|
"comment": "persister:fs",
|
||||||
|
"name": "Polly.JS",
|
||||||
|
"version": "5.0.0"
|
||||||
|
},
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_id": "0b09ec35ecae5b17a2ccda062b1d6ef5",
|
||||||
|
"_order": 0,
|
||||||
|
"cache": {},
|
||||||
|
"request": {
|
||||||
|
"bodySize": 602,
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept",
|
||||||
|
"value": "*/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "602"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "user-agent",
|
||||||
|
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "accept-encoding",
|
||||||
|
"value": "gzip,deflate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "connection",
|
||||||
|
"value": "close"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "host",
|
||||||
|
"value": "localhost:8000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 254,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"method": "POST",
|
||||||
|
"postData": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"params": [],
|
||||||
|
"text": "[{\"operationName\":\"TokenAuth\",\"variables\":{\"email\":\"client@example.com\",\"password\":\"password\"},\"query\":\"fragment User on User {\\n id\\n email\\n firstName\\n lastName\\n isStaff\\n userPermissions {\\n code\\n name\\n __typename\\n }\\n avatar {\\n url\\n __typename\\n }\\n __typename\\n}\\n\\nmutation TokenAuth($email: String!, $password: String!) {\\n tokenCreate(email: $email, password: $password) {\\n errors: accountErrors {\\n field\\n message\\n __typename\\n }\\n csrfToken\\n token\\n user {\\n ...User\\n __typename\\n }\\n __typename\\n }\\n}\\n\"}]"
|
||||||
|
},
|
||||||
|
"queryString": [],
|
||||||
|
"url": "http://localhost:8000/graphql/"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"bodySize": 616,
|
||||||
|
"content": {
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"size": 616,
|
||||||
|
"text": "[{\"data\": {\"tokenCreate\": {\"errors\": [], \"csrfToken\": \"ztCCq2djodzVWeaynv8ifQbIIB4nrM6HyInqcZ4xqoaIEfOOKUEhAqw9rVR8Cr8L\", \"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTEwNjY1MTcsImV4cCI6MTYxMTA2NjgxNywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJhY2Nlc3MiLCJ1c2VyX2lkIjoiVlhObGNqb3pNQT09IiwiaXNfc3RhZmYiOmZhbHNlfQ.lXsNnIBxZCCL843TTjn84lkWpE05o88F5q811ApjdKA\", \"user\": {\"id\": \"VXNlcjozMA==\", \"email\": \"client@example.com\", \"firstName\": \"\", \"lastName\": \"\", \"isStaff\": false, \"userPermissions\": [], \"avatar\": null, \"__typename\": \"User\"}, \"__typename\": \"CreateToken\"}}}]"
|
||||||
|
},
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"httpOnly": true,
|
||||||
|
"name": "refreshToken",
|
||||||
|
"path": "/",
|
||||||
|
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTEwNjY1MTcsImV4cCI6MTYxMzY1ODUxNywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoienRDQ3EyZGpvZHpWV2VheW52OGlmUWJJSUI0bnJNNkh5SW5xY1o0eHFvYUlFZk9PS1VFaEFxdzlyVlI4Q3I4TCJ9.hDMEK3HFSLol7rLd9dfaSCLTWgvetNDAFsb039L9PXQ"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Tue, 19 Jan 2021 14:28:37 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "WSGIServer/0.2 CPython/3.9.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "616"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "same-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_fromType": "array",
|
||||||
|
"name": "set-cookie",
|
||||||
|
"value": "refreshToken=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTEwNjY1MTcsImV4cCI6MTYxMzY1ODUxNywidG9rZW4iOiJ6MDc2QndLZkNEMmYiLCJlbWFpbCI6ImNsaWVudEBleGFtcGxlLmNvbSIsInR5cGUiOiJyZWZyZXNoIiwidXNlcl9pZCI6IlZYTmxjam96TUE9PSIsImlzX3N0YWZmIjpmYWxzZSwiY3NyZlRva2VuIjoienRDQ3EyZGpvZHpWV2VheW52OGlmUWJJSUI0bnJNNkh5SW5xY1o0eHFvYUlFZk9PS1VFaEFxdzlyVlI4Q3I4TCJ9.hDMEK3HFSLol7rLd9dfaSCLTWgvetNDAFsb039L9PXQ; HttpOnly; Path=/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headersSize": 619,
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"redirectURL": "",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"startedDateTime": "2021-01-19T14:28:37.164Z",
|
||||||
|
"time": 468,
|
||||||
|
"timings": {
|
||||||
|
"blocked": -1,
|
||||||
|
"connect": -1,
|
||||||
|
"dns": -1,
|
||||||
|
"receive": 0,
|
||||||
|
"send": 0,
|
||||||
|
"ssl": -1,
|
||||||
|
"wait": 468
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [],
|
||||||
|
"version": "1.2"
|
||||||
|
}
|
||||||
|
}
|
248
schema.graphql
248
schema.graphql
|
@ -393,6 +393,7 @@ type Attribute implements Node & ObjectWithMetadata {
|
||||||
privateMetadata: [MetadataItem]!
|
privateMetadata: [MetadataItem]!
|
||||||
metadata: [MetadataItem]!
|
metadata: [MetadataItem]!
|
||||||
inputType: AttributeInputTypeEnum
|
inputType: AttributeInputTypeEnum
|
||||||
|
entityType: AttributeEntityTypeEnum
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
type: AttributeTypeEnum
|
type: AttributeTypeEnum
|
||||||
|
@ -431,6 +432,7 @@ type AttributeCreate {
|
||||||
|
|
||||||
input AttributeCreateInput {
|
input AttributeCreateInput {
|
||||||
inputType: AttributeInputTypeEnum
|
inputType: AttributeInputTypeEnum
|
||||||
|
entityType: AttributeEntityTypeEnum
|
||||||
name: String!
|
name: String!
|
||||||
slug: String
|
slug: String
|
||||||
type: AttributeTypeEnum!
|
type: AttributeTypeEnum!
|
||||||
|
@ -450,6 +452,11 @@ type AttributeDelete {
|
||||||
attribute: Attribute
|
attribute: Attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AttributeEntityTypeEnum {
|
||||||
|
PAGE
|
||||||
|
PRODUCT
|
||||||
|
}
|
||||||
|
|
||||||
type AttributeError {
|
type AttributeError {
|
||||||
field: String
|
field: String
|
||||||
message: String
|
message: String
|
||||||
|
@ -490,6 +497,7 @@ enum AttributeInputTypeEnum {
|
||||||
DROPDOWN
|
DROPDOWN
|
||||||
MULTISELECT
|
MULTISELECT
|
||||||
FILE
|
FILE
|
||||||
|
REFERENCE
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttributeReorderValues {
|
type AttributeReorderValues {
|
||||||
|
@ -563,9 +571,9 @@ type AttributeValue implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
type: AttributeValueType @deprecated(reason: "Use the `inputType` field to determine the type of attribute's value. This field will be removed after 2020-07-31.")
|
|
||||||
translation(languageCode: LanguageCodeEnum!): AttributeValueTranslation
|
translation(languageCode: LanguageCodeEnum!): AttributeValueTranslation
|
||||||
inputType: AttributeInputTypeEnum
|
inputType: AttributeInputTypeEnum
|
||||||
|
reference: ID
|
||||||
file: File
|
file: File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,6 +606,7 @@ input AttributeValueInput {
|
||||||
values: [String]
|
values: [String]
|
||||||
file: String
|
file: String
|
||||||
contentType: String
|
contentType: String
|
||||||
|
references: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type AttributeValueTranslatableContent implements Node {
|
type AttributeValueTranslatableContent implements Node {
|
||||||
|
@ -619,13 +628,6 @@ type AttributeValueTranslation implements Node {
|
||||||
language: LanguageDisplay!
|
language: LanguageDisplay!
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AttributeValueType {
|
|
||||||
COLOR
|
|
||||||
GRADIENT
|
|
||||||
URL
|
|
||||||
STRING
|
|
||||||
}
|
|
||||||
|
|
||||||
type AttributeValueUpdate {
|
type AttributeValueUpdate {
|
||||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
attribute: Attribute
|
attribute: Attribute
|
||||||
|
@ -643,6 +645,7 @@ type BulkProductError {
|
||||||
message: String
|
message: String
|
||||||
code: ProductErrorCode!
|
code: ProductErrorCode!
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
|
values: [ID!]
|
||||||
index: Int
|
index: Int
|
||||||
warehouses: [ID!]
|
warehouses: [ID!]
|
||||||
channels: [ID!]
|
channels: [ID!]
|
||||||
|
@ -653,6 +656,7 @@ type BulkStockError {
|
||||||
message: String
|
message: String
|
||||||
code: ProductErrorCode!
|
code: ProductErrorCode!
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
|
values: [ID!]
|
||||||
index: Int
|
index: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,13 +671,13 @@ type Category implements Node & ObjectWithMetadata {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
|
||||||
slug: String!
|
slug: String!
|
||||||
parent: Category
|
parent: Category
|
||||||
level: Int!
|
level: Int!
|
||||||
privateMetadata: [MetadataItem]!
|
privateMetadata: [MetadataItem]!
|
||||||
metadata: [MetadataItem]!
|
metadata: [MetadataItem]!
|
||||||
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
ancestors(before: String, after: String, first: Int, last: Int): CategoryCountableConnection
|
ancestors(before: String, after: String, first: Int, last: Int): CategoryCountableConnection
|
||||||
products(channel: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection
|
products(channel: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection
|
||||||
url: String @deprecated(reason: "This field will be removed after 2020-07-31.")
|
url: String @deprecated(reason: "This field will be removed after 2020-07-31.")
|
||||||
|
@ -717,8 +721,7 @@ input CategoryFilterInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
input CategoryInput {
|
input CategoryInput {
|
||||||
description: String
|
description: JSONString
|
||||||
descriptionJson: JSONString
|
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
seo: SeoInput
|
seo: SeoInput
|
||||||
|
@ -743,8 +746,8 @@ type CategoryTranslatableContent implements Node {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
translation(languageCode: LanguageCodeEnum!): CategoryTranslation
|
translation(languageCode: LanguageCodeEnum!): CategoryTranslation
|
||||||
category: Category
|
category: Category
|
||||||
}
|
}
|
||||||
|
@ -760,9 +763,9 @@ type CategoryTranslation implements Node {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
|
||||||
language: LanguageDisplay!
|
language: LanguageDisplay!
|
||||||
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
type CategoryUpdate {
|
type CategoryUpdate {
|
||||||
|
@ -1047,11 +1050,11 @@ type Collection implements Node & ObjectWithMetadata {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
|
||||||
slug: String!
|
slug: String!
|
||||||
privateMetadata: [MetadataItem]!
|
privateMetadata: [MetadataItem]!
|
||||||
metadata: [MetadataItem]!
|
metadata: [MetadataItem]!
|
||||||
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
products(filter: ProductFilterInput, sortBy: ProductOrder, before: String, after: String, first: Int, last: Int): ProductCountableConnection
|
products(filter: ProductFilterInput, sortBy: ProductOrder, before: String, after: String, first: Int, last: Int): ProductCountableConnection
|
||||||
backgroundImage(size: Int): Image
|
backgroundImage(size: Int): Image
|
||||||
translation(languageCode: LanguageCodeEnum!): CollectionTranslation
|
translation(languageCode: LanguageCodeEnum!): CollectionTranslation
|
||||||
|
@ -1082,6 +1085,7 @@ type CollectionChannelListingError {
|
||||||
message: String
|
message: String
|
||||||
code: ProductErrorCode!
|
code: ProductErrorCode!
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
|
values: [ID!]
|
||||||
channels: [ID!]
|
channels: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,8 +1121,7 @@ input CollectionCreateInput {
|
||||||
isPublished: Boolean
|
isPublished: Boolean
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
description: String
|
description: JSONString
|
||||||
descriptionJson: JSONString
|
|
||||||
backgroundImage: Upload
|
backgroundImage: Upload
|
||||||
backgroundImageAlt: String
|
backgroundImageAlt: String
|
||||||
seo: SeoInput
|
seo: SeoInput
|
||||||
|
@ -1160,8 +1163,7 @@ input CollectionInput {
|
||||||
isPublished: Boolean
|
isPublished: Boolean
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
description: String
|
description: JSONString
|
||||||
descriptionJson: JSONString
|
|
||||||
backgroundImage: Upload
|
backgroundImage: Upload
|
||||||
backgroundImageAlt: String
|
backgroundImageAlt: String
|
||||||
seo: SeoInput
|
seo: SeoInput
|
||||||
|
@ -1203,8 +1205,8 @@ type CollectionTranslatableContent implements Node {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
translation(languageCode: LanguageCodeEnum!): CollectionTranslation
|
translation(languageCode: LanguageCodeEnum!): CollectionTranslation
|
||||||
collection: Collection
|
collection: Collection
|
||||||
}
|
}
|
||||||
|
@ -1220,9 +1222,9 @@ type CollectionTranslation implements Node {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
|
||||||
language: LanguageDisplay!
|
language: LanguageDisplay!
|
||||||
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectionUpdate {
|
type CollectionUpdate {
|
||||||
|
@ -1941,6 +1943,49 @@ enum ExportScope {
|
||||||
FILTER
|
FILTER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExternalAuthentication {
|
||||||
|
id: String!
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalAuthenticationUrl {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
authenticationData: JSONString
|
||||||
|
accountErrors: [AccountError!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalLogout {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
logoutData: JSONString
|
||||||
|
accountErrors: [AccountError!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalObtainAccessTokens {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
token: String
|
||||||
|
refreshToken: String
|
||||||
|
csrfToken: String
|
||||||
|
user: User
|
||||||
|
accountErrors: [AccountError!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalRefresh {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
token: String
|
||||||
|
refreshToken: String
|
||||||
|
csrfToken: String
|
||||||
|
user: User
|
||||||
|
accountErrors: [AccountError!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalVerify {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
user: User
|
||||||
|
isValid: Boolean!
|
||||||
|
verifyData: JSONString
|
||||||
|
accountErrors: [AccountError!]!
|
||||||
|
}
|
||||||
|
|
||||||
type File {
|
type File {
|
||||||
url: String!
|
url: String!
|
||||||
contentType: String
|
contentType: String
|
||||||
|
@ -1994,9 +2039,21 @@ type FulfillmentRefundProducts {
|
||||||
orderErrors: [OrderError!]!
|
orderErrors: [OrderError!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FulfillmentReturnProducts {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
returnFulfillment: Fulfillment
|
||||||
|
replaceFulfillment: Fulfillment
|
||||||
|
order: Order
|
||||||
|
replaceOrder: Order
|
||||||
|
orderErrors: [OrderError!]!
|
||||||
|
}
|
||||||
|
|
||||||
enum FulfillmentStatus {
|
enum FulfillmentStatus {
|
||||||
FULFILLED
|
FULFILLED
|
||||||
REFUNDED
|
REFUNDED
|
||||||
|
RETURNED
|
||||||
|
REPLACED
|
||||||
|
REFUNDED_AND_RETURNED
|
||||||
CANCELED
|
CANCELED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2295,10 +2352,12 @@ type Margin {
|
||||||
stop: Int
|
stop: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Menu implements Node {
|
type Menu implements Node & ObjectWithMetadata {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
slug: String!
|
slug: String!
|
||||||
|
privateMetadata: [MetadataItem]!
|
||||||
|
metadata: [MetadataItem]!
|
||||||
items: [MenuItem]
|
items: [MenuItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2365,7 +2424,7 @@ input MenuInput {
|
||||||
slug: String
|
slug: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type MenuItem implements Node {
|
type MenuItem implements Node & ObjectWithMetadata {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
menu: Menu!
|
menu: Menu!
|
||||||
|
@ -2374,6 +2433,8 @@ type MenuItem implements Node {
|
||||||
collection: Collection
|
collection: Collection
|
||||||
page: Page
|
page: Page
|
||||||
level: Int!
|
level: Int!
|
||||||
|
privateMetadata: [MetadataItem]!
|
||||||
|
metadata: [MetadataItem]!
|
||||||
children: [MenuItem]
|
children: [MenuItem]
|
||||||
url: String
|
url: String
|
||||||
translation(languageCode: LanguageCodeEnum!): MenuItemTranslation
|
translation(languageCode: LanguageCodeEnum!): MenuItemTranslation
|
||||||
|
@ -2596,6 +2657,7 @@ type Mutation {
|
||||||
productTypeBulkDelete(ids: [ID]!): ProductTypeBulkDelete
|
productTypeBulkDelete(ids: [ID]!): ProductTypeBulkDelete
|
||||||
productTypeUpdate(id: ID!, input: ProductTypeInput!): ProductTypeUpdate
|
productTypeUpdate(id: ID!, input: ProductTypeInput!): ProductTypeUpdate
|
||||||
productTypeReorderAttributes(moves: [ReorderInput]!, productTypeId: ID!, type: ProductAttributeType!): ProductTypeReorderAttributes
|
productTypeReorderAttributes(moves: [ReorderInput]!, productTypeId: ID!, type: ProductAttributeType!): ProductTypeReorderAttributes
|
||||||
|
productReorderAttributeValues(attributeId: ID!, moves: [ReorderInput]!, productId: ID!): ProductReorderAttributeValues
|
||||||
digitalContentCreate(input: DigitalContentUploadInput!, variantId: ID!): DigitalContentCreate
|
digitalContentCreate(input: DigitalContentUploadInput!, variantId: ID!): DigitalContentCreate
|
||||||
digitalContentDelete(variantId: ID!): DigitalContentDelete
|
digitalContentDelete(variantId: ID!): DigitalContentDelete
|
||||||
digitalContentUpdate(input: DigitalContentInput!, variantId: ID!): DigitalContentUpdate
|
digitalContentUpdate(input: DigitalContentInput!, variantId: ID!): DigitalContentUpdate
|
||||||
|
@ -2611,6 +2673,7 @@ type Mutation {
|
||||||
productVariantSetDefault(productId: ID!, variantId: ID!): ProductVariantSetDefault
|
productVariantSetDefault(productId: ID!, variantId: ID!): ProductVariantSetDefault
|
||||||
productVariantTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ProductVariantTranslate
|
productVariantTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ProductVariantTranslate
|
||||||
productVariantChannelListingUpdate(id: ID!, input: [ProductVariantChannelListingAddInput!]!): ProductVariantChannelListingUpdate
|
productVariantChannelListingUpdate(id: ID!, input: [ProductVariantChannelListingAddInput!]!): ProductVariantChannelListingUpdate
|
||||||
|
productVariantReorderAttributeValues(attributeId: ID!, moves: [ReorderInput]!, variantId: ID!): ProductVariantReorderAttributeValues
|
||||||
variantImageAssign(imageId: ID!, variantId: ID!): VariantImageAssign
|
variantImageAssign(imageId: ID!, variantId: ID!): VariantImageAssign
|
||||||
variantImageUnassign(imageId: ID!, variantId: ID!): VariantImageUnassign
|
variantImageUnassign(imageId: ID!, variantId: ID!): VariantImageUnassign
|
||||||
paymentCapture(amount: PositiveDecimal, paymentId: ID!): PaymentCapture
|
paymentCapture(amount: PositiveDecimal, paymentId: ID!): PaymentCapture
|
||||||
|
@ -2630,6 +2693,7 @@ type Mutation {
|
||||||
pageAttributeAssign(attributeIds: [ID!]!, pageTypeId: ID!): PageAttributeAssign
|
pageAttributeAssign(attributeIds: [ID!]!, pageTypeId: ID!): PageAttributeAssign
|
||||||
pageAttributeUnassign(attributeIds: [ID!]!, pageTypeId: ID!): PageAttributeUnassign
|
pageAttributeUnassign(attributeIds: [ID!]!, pageTypeId: ID!): PageAttributeUnassign
|
||||||
pageTypeReorderAttributes(moves: [ReorderInput!]!, pageTypeId: ID!): PageTypeReorderAttributes
|
pageTypeReorderAttributes(moves: [ReorderInput!]!, pageTypeId: ID!): PageTypeReorderAttributes
|
||||||
|
pageReorderAttributeValues(attributeId: ID!, moves: [ReorderInput]!, pageId: ID!): PageReorderAttributeValues
|
||||||
draftOrderComplete(id: ID!): DraftOrderComplete
|
draftOrderComplete(id: ID!): DraftOrderComplete
|
||||||
draftOrderCreate(input: DraftOrderCreateInput!): DraftOrderCreate
|
draftOrderCreate(input: DraftOrderCreateInput!): DraftOrderCreate
|
||||||
draftOrderDelete(id: ID!): DraftOrderDelete
|
draftOrderDelete(id: ID!): DraftOrderDelete
|
||||||
|
@ -2647,6 +2711,7 @@ type Mutation {
|
||||||
orderFulfillmentCancel(id: ID!, input: FulfillmentCancelInput!): FulfillmentCancel
|
orderFulfillmentCancel(id: ID!, input: FulfillmentCancelInput!): FulfillmentCancel
|
||||||
orderFulfillmentUpdateTracking(id: ID!, input: FulfillmentUpdateTrackingInput!): FulfillmentUpdateTracking
|
orderFulfillmentUpdateTracking(id: ID!, input: FulfillmentUpdateTrackingInput!): FulfillmentUpdateTracking
|
||||||
orderFulfillmentRefundProducts(input: OrderRefundProductsInput!, order: ID!): FulfillmentRefundProducts
|
orderFulfillmentRefundProducts(input: OrderRefundProductsInput!, order: ID!): FulfillmentRefundProducts
|
||||||
|
orderFulfillmentReturnProducts(input: OrderReturnProductsInput!, order: ID!): FulfillmentReturnProducts
|
||||||
orderMarkAsPaid(id: ID!, transactionReference: String): OrderMarkAsPaid
|
orderMarkAsPaid(id: ID!, transactionReference: String): OrderMarkAsPaid
|
||||||
orderRefund(amount: PositiveDecimal!, id: ID!): OrderRefund
|
orderRefund(amount: PositiveDecimal!, id: ID!): OrderRefund
|
||||||
orderUpdate(id: ID!, input: OrderUpdateInput!): OrderUpdate
|
orderUpdate(id: ID!, input: OrderUpdateInput!): OrderUpdate
|
||||||
|
@ -2743,6 +2808,11 @@ type Mutation {
|
||||||
tokenRefresh(csrfToken: String, refreshToken: String): RefreshToken
|
tokenRefresh(csrfToken: String, refreshToken: String): RefreshToken
|
||||||
tokenVerify(token: String!): VerifyToken
|
tokenVerify(token: String!): VerifyToken
|
||||||
tokensDeactivateAll: DeactivateAllUserTokens
|
tokensDeactivateAll: DeactivateAllUserTokens
|
||||||
|
externalAuthenticationUrl(input: JSONString!, pluginId: String!): ExternalAuthenticationUrl
|
||||||
|
externalObtainAccessTokens(input: JSONString!, pluginId: String!): ExternalObtainAccessTokens
|
||||||
|
externalRefresh(input: JSONString!, pluginId: String!): ExternalRefresh
|
||||||
|
externalLogout(input: JSONString!, pluginId: String!): ExternalLogout
|
||||||
|
externalVerify(input: JSONString!, pluginId: String!): ExternalVerify
|
||||||
requestPasswordReset(email: String!, redirectUrl: String!): RequestPasswordReset
|
requestPasswordReset(email: String!, redirectUrl: String!): RequestPasswordReset
|
||||||
confirmAccount(email: String!, token: String!): ConfirmAccount
|
confirmAccount(email: String!, token: String!): ConfirmAccount
|
||||||
setPassword(email: String!, password: String!, token: String!): SetPassword
|
setPassword(email: String!, password: String!, token: String!): SetPassword
|
||||||
|
@ -2812,7 +2882,8 @@ type Order implements Node & ObjectWithMetadata {
|
||||||
shippingMethod: ShippingMethod
|
shippingMethod: ShippingMethod
|
||||||
shippingMethodName: String
|
shippingMethodName: String
|
||||||
channel: Channel!
|
channel: Channel!
|
||||||
shippingPrice: TaxedMoney
|
shippingPrice: TaxedMoney!
|
||||||
|
shippingTaxRate: Float!
|
||||||
token: String!
|
token: String!
|
||||||
voucher: Voucher
|
voucher: Voucher
|
||||||
giftCards: [GiftCard]
|
giftCards: [GiftCard]
|
||||||
|
@ -2831,16 +2902,16 @@ type Order implements Node & ObjectWithMetadata {
|
||||||
availableShippingMethods: [ShippingMethod]
|
availableShippingMethods: [ShippingMethod]
|
||||||
invoices: [Invoice]
|
invoices: [Invoice]
|
||||||
number: String
|
number: String
|
||||||
isPaid: Boolean
|
isPaid: Boolean!
|
||||||
paymentStatus: PaymentChargeStatusEnum
|
paymentStatus: PaymentChargeStatusEnum!
|
||||||
paymentStatusDisplay: String
|
paymentStatusDisplay: String!
|
||||||
payments: [Payment]
|
payments: [Payment]
|
||||||
total: TaxedMoney
|
total: TaxedMoney!
|
||||||
subtotal: TaxedMoney
|
subtotal: TaxedMoney!
|
||||||
statusDisplay: String
|
statusDisplay: String
|
||||||
canFinalize: Boolean!
|
canFinalize: Boolean!
|
||||||
totalAuthorized: Money
|
totalAuthorized: Money!
|
||||||
totalCaptured: Money
|
totalCaptured: Money!
|
||||||
events: [OrderEvent]
|
events: [OrderEvent]
|
||||||
totalBalance: Money!
|
totalBalance: Money!
|
||||||
userEmail: String
|
userEmail: String
|
||||||
|
@ -2909,6 +2980,7 @@ input OrderDraftFilterInput {
|
||||||
customer: String
|
customer: String
|
||||||
created: DateRangeInput
|
created: DateRangeInput
|
||||||
search: String
|
search: String
|
||||||
|
channels: [ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderError {
|
type OrderError {
|
||||||
|
@ -2944,8 +3016,7 @@ enum OrderErrorCode {
|
||||||
UNIQUE
|
UNIQUE
|
||||||
VOID_INACTIVE_PAYMENT
|
VOID_INACTIVE_PAYMENT
|
||||||
ZERO_QUANTITY
|
ZERO_QUANTITY
|
||||||
INVALID_REFUND_QUANTITY
|
INVALID_QUANTITY
|
||||||
CANNOT_REFUND_FULFILLMENT_LINE
|
|
||||||
INSUFFICIENT_STOCK
|
INSUFFICIENT_STOCK
|
||||||
DUPLICATED_INPUT_ITEM
|
DUPLICATED_INPUT_ITEM
|
||||||
NOT_AVAILABLE_IN_CHANNEL
|
NOT_AVAILABLE_IN_CHANNEL
|
||||||
|
@ -2973,6 +3044,7 @@ type OrderEvent implements Node {
|
||||||
warehouse: Warehouse
|
warehouse: Warehouse
|
||||||
transactionReference: String
|
transactionReference: String
|
||||||
shippingCostsIncluded: Boolean
|
shippingCostsIncluded: Boolean
|
||||||
|
relatedOrder: Order
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderEventCountableConnection {
|
type OrderEventCountableConnection {
|
||||||
|
@ -3006,6 +3078,7 @@ enum OrderEventsEmailsEnum {
|
||||||
|
|
||||||
enum OrderEventsEnum {
|
enum OrderEventsEnum {
|
||||||
DRAFT_CREATED
|
DRAFT_CREATED
|
||||||
|
DRAFT_CREATED_FROM_REPLACE
|
||||||
DRAFT_ADDED_PRODUCTS
|
DRAFT_ADDED_PRODUCTS
|
||||||
DRAFT_REMOVED_PRODUCTS
|
DRAFT_REMOVED_PRODUCTS
|
||||||
PLACED
|
PLACED
|
||||||
|
@ -3014,6 +3087,7 @@ enum OrderEventsEnum {
|
||||||
CANCELED
|
CANCELED
|
||||||
ORDER_MARKED_AS_PAID
|
ORDER_MARKED_AS_PAID
|
||||||
ORDER_FULLY_PAID
|
ORDER_FULLY_PAID
|
||||||
|
ORDER_REPLACEMENT_CREATED
|
||||||
UPDATED_ADDRESS
|
UPDATED_ADDRESS
|
||||||
EMAIL_SENT
|
EMAIL_SENT
|
||||||
CONFIRMED
|
CONFIRMED
|
||||||
|
@ -3031,6 +3105,8 @@ enum OrderEventsEnum {
|
||||||
FULFILLMENT_RESTOCKED_ITEMS
|
FULFILLMENT_RESTOCKED_ITEMS
|
||||||
FULFILLMENT_FULFILLED_ITEMS
|
FULFILLMENT_FULFILLED_ITEMS
|
||||||
FULFILLMENT_REFUNDED
|
FULFILLMENT_REFUNDED
|
||||||
|
FULFILLMENT_RETURNED
|
||||||
|
FULFILLMENT_REPLACED
|
||||||
TRACKING_UPDATED
|
TRACKING_UPDATED
|
||||||
NOTE_ADDED
|
NOTE_ADDED
|
||||||
OTHER
|
OTHER
|
||||||
|
@ -3078,8 +3154,8 @@ type OrderLine implements Node {
|
||||||
taxRate: Float!
|
taxRate: Float!
|
||||||
digitalContentUrl: DigitalContentUrl
|
digitalContentUrl: DigitalContentUrl
|
||||||
thumbnail(size: Int): Image
|
thumbnail(size: Int): Image
|
||||||
unitPrice: TaxedMoney
|
unitPrice: TaxedMoney!
|
||||||
totalPrice: TaxedMoney
|
totalPrice: TaxedMoney!
|
||||||
variant: ProductVariant
|
variant: ProductVariant
|
||||||
translatedProductName: String!
|
translatedProductName: String!
|
||||||
translatedVariantName: String!
|
translatedVariantName: String!
|
||||||
|
@ -3124,6 +3200,26 @@ input OrderRefundProductsInput {
|
||||||
includeShippingCosts: Boolean = false
|
includeShippingCosts: Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input OrderReturnFulfillmentLineInput {
|
||||||
|
fulfillmentLineId: ID!
|
||||||
|
quantity: Int!
|
||||||
|
replace: Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
input OrderReturnLineInput {
|
||||||
|
orderLineId: ID!
|
||||||
|
quantity: Int!
|
||||||
|
replace: Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
input OrderReturnProductsInput {
|
||||||
|
orderLines: [OrderReturnLineInput!]
|
||||||
|
fulfillmentLines: [OrderReturnFulfillmentLineInput!]
|
||||||
|
amountToRefund: PositiveDecimal
|
||||||
|
includeShippingCosts: Boolean = false
|
||||||
|
refund: Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
type OrderSettings {
|
type OrderSettings {
|
||||||
automaticallyConfirmAllNewOrders: Boolean!
|
automaticallyConfirmAllNewOrders: Boolean!
|
||||||
}
|
}
|
||||||
|
@ -3166,6 +3262,8 @@ enum OrderStatus {
|
||||||
UNCONFIRMED
|
UNCONFIRMED
|
||||||
UNFULFILLED
|
UNFULFILLED
|
||||||
PARTIALLY_FULFILLED
|
PARTIALLY_FULFILLED
|
||||||
|
PARTIALLY_RETURNED
|
||||||
|
RETURNED
|
||||||
FULFILLED
|
FULFILLED
|
||||||
CANCELED
|
CANCELED
|
||||||
}
|
}
|
||||||
|
@ -3213,8 +3311,7 @@ type Page implements Node & ObjectWithMetadata {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String!
|
title: String!
|
||||||
content: String!
|
content: JSONString!
|
||||||
contentJson: JSONString!
|
|
||||||
publicationDate: Date
|
publicationDate: Date
|
||||||
isPublished: Boolean!
|
isPublished: Boolean!
|
||||||
slug: String!
|
slug: String!
|
||||||
|
@ -3222,6 +3319,7 @@ type Page implements Node & ObjectWithMetadata {
|
||||||
created: DateTime!
|
created: DateTime!
|
||||||
privateMetadata: [MetadataItem]!
|
privateMetadata: [MetadataItem]!
|
||||||
metadata: [MetadataItem]!
|
metadata: [MetadataItem]!
|
||||||
|
contentJson: String! @deprecated(reason: "Will be removed in Saleor 4.0. Use the `content` field instead.")
|
||||||
translation(languageCode: LanguageCodeEnum!): PageTranslation
|
translation(languageCode: LanguageCodeEnum!): PageTranslation
|
||||||
attributes: [SelectedAttribute!]!
|
attributes: [SelectedAttribute!]!
|
||||||
}
|
}
|
||||||
|
@ -3270,8 +3368,7 @@ type PageCreate {
|
||||||
input PageCreateInput {
|
input PageCreateInput {
|
||||||
slug: String
|
slug: String
|
||||||
title: String
|
title: String
|
||||||
content: String
|
content: JSONString
|
||||||
contentJson: JSONString
|
|
||||||
attributes: [AttributeValueInput!]
|
attributes: [AttributeValueInput!]
|
||||||
isPublished: Boolean
|
isPublished: Boolean
|
||||||
publicationDate: String
|
publicationDate: String
|
||||||
|
@ -3290,6 +3387,7 @@ type PageError {
|
||||||
message: String
|
message: String
|
||||||
code: PageErrorCode!
|
code: PageErrorCode!
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
|
values: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PageErrorCode {
|
enum PageErrorCode {
|
||||||
|
@ -3316,14 +3414,19 @@ type PageInfo {
|
||||||
input PageInput {
|
input PageInput {
|
||||||
slug: String
|
slug: String
|
||||||
title: String
|
title: String
|
||||||
content: String
|
content: JSONString
|
||||||
contentJson: JSONString
|
|
||||||
attributes: [AttributeValueInput!]
|
attributes: [AttributeValueInput!]
|
||||||
isPublished: Boolean
|
isPublished: Boolean
|
||||||
publicationDate: String
|
publicationDate: String
|
||||||
seo: SeoInput
|
seo: SeoInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PageReorderAttributeValues {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
page: Page
|
||||||
|
pageErrors: [PageError!]!
|
||||||
|
}
|
||||||
|
|
||||||
enum PageSortField {
|
enum PageSortField {
|
||||||
TITLE
|
TITLE
|
||||||
SLUG
|
SLUG
|
||||||
|
@ -3342,8 +3445,8 @@ type PageTranslatableContent implements Node {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String!
|
title: String!
|
||||||
content: String!
|
content: JSONString!
|
||||||
contentJson: JSONString!
|
contentJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `content` field instead.")
|
||||||
translation(languageCode: LanguageCodeEnum!): PageTranslation
|
translation(languageCode: LanguageCodeEnum!): PageTranslation
|
||||||
page: Page
|
page: Page
|
||||||
}
|
}
|
||||||
|
@ -3359,17 +3462,16 @@ type PageTranslation implements Node {
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String!
|
title: String!
|
||||||
content: String!
|
content: JSONString!
|
||||||
contentJson: JSONString!
|
|
||||||
language: LanguageDisplay!
|
language: LanguageDisplay!
|
||||||
|
contentJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `content` field instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
input PageTranslationInput {
|
input PageTranslationInput {
|
||||||
seoTitle: String
|
seoTitle: String
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
title: String
|
title: String
|
||||||
content: String
|
content: JSONString
|
||||||
contentJson: JSONString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageType implements Node & ObjectWithMetadata {
|
type PageType implements Node & ObjectWithMetadata {
|
||||||
|
@ -3737,8 +3839,7 @@ type Product implements Node & ObjectWithMetadata {
|
||||||
seoTitle: String
|
seoTitle: String
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
|
||||||
productType: ProductType!
|
productType: ProductType!
|
||||||
slug: String!
|
slug: String!
|
||||||
category: Category
|
category: Category
|
||||||
|
@ -3749,6 +3850,7 @@ type Product implements Node & ObjectWithMetadata {
|
||||||
rating: Float
|
rating: Float
|
||||||
privateMetadata: [MetadataItem]!
|
privateMetadata: [MetadataItem]!
|
||||||
metadata: [MetadataItem]!
|
metadata: [MetadataItem]!
|
||||||
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
url: String! @deprecated(reason: "This field will be removed after 2020-07-31.")
|
url: String! @deprecated(reason: "This field will be removed after 2020-07-31.")
|
||||||
thumbnail(size: Int): Image
|
thumbnail(size: Int): Image
|
||||||
pricing: ProductPricingInfo
|
pricing: ProductPricingInfo
|
||||||
|
@ -3821,6 +3923,7 @@ type ProductChannelListingError {
|
||||||
message: String
|
message: String
|
||||||
code: ProductErrorCode!
|
code: ProductErrorCode!
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
|
values: [ID!]
|
||||||
channels: [ID!]
|
channels: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3857,8 +3960,7 @@ input ProductCreateInput {
|
||||||
category: ID
|
category: ID
|
||||||
chargeTaxes: Boolean
|
chargeTaxes: Boolean
|
||||||
collections: [ID]
|
collections: [ID]
|
||||||
description: String
|
description: JSONString
|
||||||
descriptionJson: JSONString
|
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
taxCode: String
|
taxCode: String
|
||||||
|
@ -3879,6 +3981,7 @@ type ProductError {
|
||||||
message: String
|
message: String
|
||||||
code: ProductErrorCode!
|
code: ProductErrorCode!
|
||||||
attributes: [ID!]
|
attributes: [ID!]
|
||||||
|
values: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProductErrorCode {
|
enum ProductErrorCode {
|
||||||
|
@ -3988,8 +4091,7 @@ input ProductInput {
|
||||||
category: ID
|
category: ID
|
||||||
chargeTaxes: Boolean
|
chargeTaxes: Boolean
|
||||||
collections: [ID]
|
collections: [ID]
|
||||||
description: String
|
description: JSONString
|
||||||
descriptionJson: JSONString
|
|
||||||
name: String
|
name: String
|
||||||
slug: String
|
slug: String
|
||||||
taxCode: String
|
taxCode: String
|
||||||
|
@ -4026,6 +4128,12 @@ type ProductPricingInfo {
|
||||||
priceRangeLocalCurrency: TaxedMoneyRange
|
priceRangeLocalCurrency: TaxedMoneyRange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProductReorderAttributeValues {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
product: Product
|
||||||
|
productErrors: [ProductError!]!
|
||||||
|
}
|
||||||
|
|
||||||
input ProductStockFilterInput {
|
input ProductStockFilterInput {
|
||||||
warehouseIds: [ID!]
|
warehouseIds: [ID!]
|
||||||
quantity: IntRangeInput
|
quantity: IntRangeInput
|
||||||
|
@ -4036,8 +4144,8 @@ type ProductTranslatableContent implements Node {
|
||||||
seoTitle: String
|
seoTitle: String
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
translation(languageCode: LanguageCodeEnum!): ProductTranslation
|
translation(languageCode: LanguageCodeEnum!): ProductTranslation
|
||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
@ -4053,9 +4161,9 @@ type ProductTranslation implements Node {
|
||||||
seoTitle: String
|
seoTitle: String
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: JSONString!
|
||||||
descriptionJson: JSONString!
|
|
||||||
language: LanguageDisplay!
|
language: LanguageDisplay!
|
||||||
|
descriptionJson: String @deprecated(reason: "Will be removed in Saleor 4.0. Use the `description` field instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductType implements Node & ObjectWithMetadata {
|
type ProductType implements Node & ObjectWithMetadata {
|
||||||
|
@ -4278,6 +4386,12 @@ type ProductVariantReorder {
|
||||||
productErrors: [ProductError!]!
|
productErrors: [ProductError!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProductVariantReorderAttributeValues {
|
||||||
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
|
productVariant: ProductVariant
|
||||||
|
productErrors: [ProductError!]!
|
||||||
|
}
|
||||||
|
|
||||||
type ProductVariantSetDefault {
|
type ProductVariantSetDefault {
|
||||||
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.")
|
||||||
product: Product
|
product: Product
|
||||||
|
@ -4405,7 +4519,7 @@ type Query {
|
||||||
permissionGroup(id: ID!): Group
|
permissionGroup(id: ID!): Group
|
||||||
me: User
|
me: User
|
||||||
staffUsers(filter: StaffUserInput, sortBy: UserSortingInput, before: String, after: String, first: Int, last: Int): UserCountableConnection
|
staffUsers(filter: StaffUserInput, sortBy: UserSortingInput, before: String, after: String, first: Int, last: Int): UserCountableConnection
|
||||||
user(id: ID!): User
|
user(id: ID, email: String): User
|
||||||
_entities(representations: [_Any]): [_Entity]
|
_entities(representations: [_Any]): [_Entity]
|
||||||
_service: _Service
|
_service: _Service
|
||||||
}
|
}
|
||||||
|
@ -4840,6 +4954,7 @@ input ShippingZoneUpdateInput {
|
||||||
|
|
||||||
type Shop {
|
type Shop {
|
||||||
availablePaymentGateways(currency: String): [PaymentGateway!]!
|
availablePaymentGateways(currency: String): [PaymentGateway!]!
|
||||||
|
availableExternalAuthentications: [ExternalAuthentication!]!
|
||||||
availableShippingMethods(channel: String!, address: AddressInput): [ShippingMethod]
|
availableShippingMethods(channel: String!, address: AddressInput): [ShippingMethod]
|
||||||
geolocalization: Geolocalization
|
geolocalization: Geolocalization
|
||||||
countries(languageCode: LanguageCodeEnum): [CountryDisplay!]!
|
countries(languageCode: LanguageCodeEnum): [CountryDisplay!]!
|
||||||
|
@ -5215,8 +5330,7 @@ input TranslationInput {
|
||||||
seoTitle: String
|
seoTitle: String
|
||||||
seoDescription: String
|
seoDescription: String
|
||||||
name: String
|
name: String
|
||||||
description: String
|
description: JSONString
|
||||||
descriptionJson: JSONString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scalar UUID
|
scalar UUID
|
||||||
|
@ -5547,7 +5661,7 @@ type VoucherUpdate {
|
||||||
voucher: Voucher
|
voucher: Voucher
|
||||||
}
|
}
|
||||||
|
|
||||||
type Warehouse implements Node {
|
type Warehouse implements Node & ObjectWithMetadata {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
slug: String!
|
slug: String!
|
||||||
|
@ -5555,6 +5669,8 @@ type Warehouse implements Node {
|
||||||
shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection!
|
shippingZones(before: String, after: String, first: Int, last: Int): ShippingZoneCountableConnection!
|
||||||
address: Address!
|
address: Address!
|
||||||
email: String!
|
email: String!
|
||||||
|
privateMetadata: [MetadataItem]!
|
||||||
|
metadata: [MetadataItem]!
|
||||||
}
|
}
|
||||||
|
|
||||||
input WarehouseAddressInput {
|
input WarehouseAddressInput {
|
||||||
|
|
|
@ -79,6 +79,7 @@ export const appDeleteFailedInstallationMutation = gql`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const appFetchMutation = gql`
|
export const appFetchMutation = gql`
|
||||||
|
${appErrorFragment}
|
||||||
mutation AppFetch($manifestUrl: String!) {
|
mutation AppFetch($manifestUrl: String!) {
|
||||||
appFetchManifest(manifestUrl: $manifestUrl) {
|
appFetchManifest(manifestUrl: $manifestUrl) {
|
||||||
manifest {
|
manifest {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Card from "@material-ui/core/Card";
|
import Card from "@material-ui/core/Card";
|
||||||
import CardContent from "@material-ui/core/CardContent";
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||||
|
@ -7,16 +8,90 @@ import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import SingleSelectField from "@saleor/components/SingleSelectField";
|
import SingleSelectField from "@saleor/components/SingleSelectField";
|
||||||
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
import {
|
||||||
|
AttributeEntityTypeEnum,
|
||||||
|
AttributeInputTypeEnum
|
||||||
|
} from "@saleor/types/globalTypes";
|
||||||
import { getFormErrors } from "@saleor/utils/errors";
|
import { getFormErrors } from "@saleor/utils/errors";
|
||||||
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
|
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
|
|
||||||
import { getAttributeSlugErrorMessage } from "../../errors";
|
import { getAttributeSlugErrorMessage } from "../../errors";
|
||||||
import { AttributePageFormData } from "../AttributePage";
|
import { AttributePageFormData } from "../AttributePage";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
attributeLabel: {
|
||||||
|
defaultMessage: "Default Label",
|
||||||
|
description: "attribute's label"
|
||||||
|
},
|
||||||
|
attributeSlug: {
|
||||||
|
defaultMessage: "Attribute Code",
|
||||||
|
description: "attribute's slug short code label"
|
||||||
|
},
|
||||||
|
attributeSlugHelperText: {
|
||||||
|
defaultMessage: "This is used internally. Make sure you don’t use spaces",
|
||||||
|
description: "attribute slug input field helper text"
|
||||||
|
},
|
||||||
|
entityType: {
|
||||||
|
defaultMessage: "Entity",
|
||||||
|
description: "attribute's editor component entity"
|
||||||
|
},
|
||||||
|
inputType: {
|
||||||
|
defaultMessage: "Catalog Input type for Store Owner",
|
||||||
|
description: "attribute's editor component"
|
||||||
|
},
|
||||||
|
valueRequired: {
|
||||||
|
defaultMessage: "Value Required",
|
||||||
|
description: "check to require attribute to have value"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputTypeMessages = defineMessages({
|
||||||
|
dropdown: {
|
||||||
|
defaultMessage: "Dropdown",
|
||||||
|
description: "product attribute type"
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
defaultMessage: "File",
|
||||||
|
description: "file attribute type"
|
||||||
|
},
|
||||||
|
multiselect: {
|
||||||
|
defaultMessage: "Multiple Select",
|
||||||
|
description: "product attribute type"
|
||||||
|
},
|
||||||
|
references: {
|
||||||
|
defaultMessage: "References",
|
||||||
|
description: "references attribute type"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const entityTypeMessages = defineMessages({
|
||||||
|
page: {
|
||||||
|
defaultMessage: "Pages",
|
||||||
|
description: "page attribute entity type"
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
defaultMessage: "Products",
|
||||||
|
description: "product attribute entity type"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
inputTypeSection: {
|
||||||
|
columnGap: theme.spacing(2) + "px",
|
||||||
|
display: "flex",
|
||||||
|
[theme.breakpoints.down("md")]: {
|
||||||
|
flexFlow: "wrap",
|
||||||
|
rowGap: theme.spacing(3) + "px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "AttributeDetails" }
|
||||||
|
);
|
||||||
|
|
||||||
export interface AttributeDetailsProps {
|
export interface AttributeDetailsProps {
|
||||||
canChangeType: boolean;
|
canChangeType: boolean;
|
||||||
data: AttributePageFormData;
|
data: AttributePageFormData;
|
||||||
|
@ -25,39 +100,43 @@ export interface AttributeDetailsProps {
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttributeDetails: React.FC<AttributeDetailsProps> = ({
|
const AttributeDetails: React.FC<AttributeDetailsProps> = props => {
|
||||||
canChangeType,
|
const { canChangeType, data, disabled, errors, onChange } = props;
|
||||||
data,
|
const classes = useStyles(props);
|
||||||
disabled,
|
|
||||||
errors,
|
|
||||||
onChange
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const inputTypeChoices = [
|
const inputTypeChoices = [
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage(inputTypeMessages.dropdown),
|
||||||
defaultMessage: "Dropdown",
|
|
||||||
description: "product attribute type"
|
|
||||||
}),
|
|
||||||
value: AttributeInputTypeEnum.DROPDOWN
|
value: AttributeInputTypeEnum.DROPDOWN
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage(inputTypeMessages.multiselect),
|
||||||
defaultMessage: "Multiple Select",
|
|
||||||
description: "product attribute type"
|
|
||||||
}),
|
|
||||||
value: AttributeInputTypeEnum.MULTISELECT
|
value: AttributeInputTypeEnum.MULTISELECT
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage(inputTypeMessages.file),
|
||||||
defaultMessage: "File",
|
|
||||||
description: "file attribute type"
|
|
||||||
}),
|
|
||||||
value: AttributeInputTypeEnum.FILE
|
value: AttributeInputTypeEnum.FILE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage(inputTypeMessages.references),
|
||||||
|
value: AttributeInputTypeEnum.REFERENCE
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const entityTypeChoices = [
|
||||||
|
{
|
||||||
|
label: intl.formatMessage(entityTypeMessages.page),
|
||||||
|
value: AttributeEntityTypeEnum.PAGE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage(entityTypeMessages.product),
|
||||||
|
value: AttributeEntityTypeEnum.PRODUCT
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const formErrors = getFormErrors(["name", "slug", "inputType"], errors);
|
const formErrors = getFormErrors(
|
||||||
|
["name", "slug", "inputType", "entityType"],
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -68,10 +147,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!formErrors.name}
|
error={!!formErrors.name}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.attributeLabel)}
|
||||||
defaultMessage: "Default Label",
|
|
||||||
description: "attribute's label"
|
|
||||||
})}
|
|
||||||
name={"name" as keyof AttributePageFormData}
|
name={"name" as keyof AttributePageFormData}
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText={getAttributeErrorMessage(formErrors.name, intl)}
|
helperText={getAttributeErrorMessage(formErrors.name, intl)}
|
||||||
|
@ -82,45 +158,46 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!formErrors.slug}
|
error={!!formErrors.slug}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.attributeSlug)}
|
||||||
defaultMessage: "Attribute Code",
|
|
||||||
description: "attribute's slug short code label"
|
|
||||||
})}
|
|
||||||
name={"slug" as keyof AttributePageFormData}
|
name={"slug" as keyof AttributePageFormData}
|
||||||
placeholder={slugify(data.name).toLowerCase()}
|
placeholder={slugify(data.name).toLowerCase()}
|
||||||
fullWidth
|
fullWidth
|
||||||
helperText={
|
helperText={
|
||||||
getAttributeSlugErrorMessage(formErrors.slug, intl) ||
|
getAttributeSlugErrorMessage(formErrors.slug, intl) ||
|
||||||
intl.formatMessage({
|
intl.formatMessage(messages.attributeSlugHelperText)
|
||||||
defaultMessage:
|
|
||||||
"This is used internally. Make sure you don’t use spaces",
|
|
||||||
description: "attribute slug input field helper text"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
value={data.slug}
|
value={data.slug}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
|
<div className={classes.inputTypeSection}>
|
||||||
<SingleSelectField
|
<SingleSelectField
|
||||||
choices={inputTypeChoices}
|
choices={inputTypeChoices}
|
||||||
disabled={disabled || !canChangeType}
|
disabled={disabled || !canChangeType}
|
||||||
error={!!formErrors.inputType}
|
error={!!formErrors.inputType}
|
||||||
hint={getAttributeErrorMessage(formErrors.inputType, intl)}
|
hint={getAttributeErrorMessage(formErrors.inputType, intl)}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.inputType)}
|
||||||
defaultMessage: "Catalog Input type for Store Owner",
|
|
||||||
description: "attribute's editor component"
|
|
||||||
})}
|
|
||||||
name="inputType"
|
name="inputType"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={data.inputType}
|
value={data.inputType}
|
||||||
/>
|
/>
|
||||||
|
{data.inputType === AttributeInputTypeEnum.REFERENCE && (
|
||||||
|
<SingleSelectField
|
||||||
|
choices={entityTypeChoices}
|
||||||
|
disabled={disabled || !canChangeType}
|
||||||
|
error={!!formErrors.entityType}
|
||||||
|
hint={getAttributeErrorMessage(formErrors.entityType, intl)}
|
||||||
|
label={intl.formatMessage(messages.entityType)}
|
||||||
|
name="entityType"
|
||||||
|
onChange={onChange}
|
||||||
|
value={data.entityType}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
name={"valueRequired" as keyof AttributePageFormData}
|
name={"valueRequired" as keyof AttributePageFormData}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.valueRequired)}
|
||||||
defaultMessage: "Value Required",
|
|
||||||
description: "check to require attribute to have value"
|
|
||||||
})}
|
|
||||||
checked={data.valueRequired}
|
checked={data.valueRequired}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES } from "@saleor/attributes/utils/data";
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
|
@ -17,6 +18,7 @@ import { sectionNames } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { ReorderAction } from "@saleor/types";
|
import { ReorderAction } from "@saleor/types";
|
||||||
import {
|
import {
|
||||||
|
AttributeEntityTypeEnum,
|
||||||
AttributeInputTypeEnum,
|
AttributeInputTypeEnum,
|
||||||
AttributeTypeEnum
|
AttributeTypeEnum
|
||||||
} from "@saleor/types/globalTypes";
|
} from "@saleor/types/globalTypes";
|
||||||
|
@ -51,6 +53,7 @@ export interface AttributePageFormData extends MetadataFormData {
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
inputType: AttributeInputTypeEnum;
|
inputType: AttributeInputTypeEnum;
|
||||||
|
entityType: AttributeEntityTypeEnum;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
@ -84,6 +87,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
attribute === null
|
attribute === null
|
||||||
? {
|
? {
|
||||||
availableInGrid: true,
|
availableInGrid: true,
|
||||||
|
entityType: null,
|
||||||
filterableInDashboard: true,
|
filterableInDashboard: true,
|
||||||
filterableInStorefront: true,
|
filterableInStorefront: true,
|
||||||
inputType: AttributeInputTypeEnum.DROPDOWN,
|
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||||
|
@ -98,6 +102,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
availableInGrid: maybe(() => attribute.availableInGrid, true),
|
availableInGrid: maybe(() => attribute.availableInGrid, true),
|
||||||
|
entityType: attribute?.entityType ?? null,
|
||||||
filterableInDashboard: maybe(
|
filterableInDashboard: maybe(
|
||||||
() => attribute.filterableInDashboard,
|
() => attribute.filterableInDashboard,
|
||||||
true
|
true
|
||||||
|
@ -172,7 +177,9 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
errors={errors}
|
errors={errors}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
{data.inputType !== AttributeInputTypeEnum.FILE && (
|
{ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(
|
||||||
|
data.inputType
|
||||||
|
) && (
|
||||||
<>
|
<>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<AttributeValues
|
<AttributeValues
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Card from "@material-ui/core/Card";
|
||||||
import CardContent from "@material-ui/core/CardContent";
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES } from "@saleor/attributes/utils/data";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||||
|
@ -10,17 +11,59 @@ import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import {
|
import { AttributeTypeEnum } from "@saleor/types/globalTypes";
|
||||||
AttributeInputTypeEnum,
|
|
||||||
AttributeTypeEnum
|
|
||||||
} from "@saleor/types/globalTypes";
|
|
||||||
import { getFormErrors } from "@saleor/utils/errors";
|
import { getFormErrors } from "@saleor/utils/errors";
|
||||||
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
|
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { AttributePageFormData } from "../AttributePage";
|
import { AttributePageFormData } from "../AttributePage";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
availableInGrid: {
|
||||||
|
defaultMessage: "Add to Column Options",
|
||||||
|
description: "add attribute as column in product list table"
|
||||||
|
},
|
||||||
|
availableInGridCaption: {
|
||||||
|
defaultMessage:
|
||||||
|
"If enabled this attribute can be used as a column in product table.",
|
||||||
|
description: "caption"
|
||||||
|
},
|
||||||
|
dashboardPropertiesTitle: {
|
||||||
|
defaultMessage: "Dashboard Properties",
|
||||||
|
description: "attribute properties regarding dashboard"
|
||||||
|
},
|
||||||
|
filterableInDashboard: {
|
||||||
|
defaultMessage: "Use in Filtering",
|
||||||
|
description: "use attribute in filtering"
|
||||||
|
},
|
||||||
|
filterableInDashboardCaption: {
|
||||||
|
defaultMessage:
|
||||||
|
"If enabled, you’ll be able to use this attribute to filter products in product list.",
|
||||||
|
description: "caption"
|
||||||
|
},
|
||||||
|
filterableInStorefront: {
|
||||||
|
defaultMessage: "Use in Faceted Navigation",
|
||||||
|
description: "attribute is filterable in storefront"
|
||||||
|
},
|
||||||
|
storefrontPropertiesTitle: {
|
||||||
|
defaultMessage: "Storefront Properties",
|
||||||
|
description: "attribute properties regarding storefront"
|
||||||
|
},
|
||||||
|
storefrontSearchPosition: {
|
||||||
|
defaultMessage: "Position in faceted navigation",
|
||||||
|
description: "attribute position in storefront filters"
|
||||||
|
},
|
||||||
|
visibleInStorefront: {
|
||||||
|
defaultMessage: "Public",
|
||||||
|
description: "attribute visibility in storefront"
|
||||||
|
},
|
||||||
|
visibleInStorefrontCaption: {
|
||||||
|
defaultMessage: "If enabled, attribute will be accessible to customers.",
|
||||||
|
description: "caption"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export interface AttributePropertiesProps {
|
export interface AttributePropertiesProps {
|
||||||
data: AttributePageFormData;
|
data: AttributePageFormData;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
@ -38,6 +81,14 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
|
|
||||||
const formErrors = getFormErrors(["storefrontSearchPosition"], errors);
|
const formErrors = getFormErrors(["storefrontSearchPosition"], errors);
|
||||||
|
|
||||||
|
const dashboardProperties = ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(
|
||||||
|
data.inputType
|
||||||
|
);
|
||||||
|
|
||||||
|
const storefrontFacetedNavigationProperties =
|
||||||
|
ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(data.inputType) &&
|
||||||
|
data.type === AttributeTypeEnum.PRODUCT_TYPE;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle title={intl.formatMessage(commonMessages.properties)} />
|
<CardTitle title={intl.formatMessage(commonMessages.properties)} />
|
||||||
|
@ -74,21 +125,14 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
<FormattedMessage
|
<FormattedMessage {...messages.storefrontPropertiesTitle} />
|
||||||
defaultMessage="Storefront Properties"
|
|
||||||
description="attribute properties regarding storefront"
|
|
||||||
/>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Hr />
|
<Hr />
|
||||||
{data.inputType !== AttributeInputTypeEnum.FILE &&
|
{storefrontFacetedNavigationProperties && (
|
||||||
data.type === AttributeTypeEnum.PRODUCT_TYPE && (
|
|
||||||
<>
|
<>
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
name={"filterableInStorefront" as keyof FormData}
|
name={"filterableInStorefront" as keyof FormData}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.filterableInStorefront)}
|
||||||
defaultMessage: "Use in Faceted Navigation",
|
|
||||||
description: "attribute is filterable in storefront"
|
|
||||||
})}
|
|
||||||
checked={data.filterableInStorefront}
|
checked={data.filterableInStorefront}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -107,10 +151,7 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
name={
|
name={
|
||||||
"storefrontSearchPosition" as keyof AttributePageFormData
|
"storefrontSearchPosition" as keyof AttributePageFormData
|
||||||
}
|
}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.storefrontSearchPosition)}
|
||||||
defaultMessage: "Position in faceted navigation",
|
|
||||||
description: "attribute position in storefront filters"
|
|
||||||
})}
|
|
||||||
value={data.storefrontSearchPosition}
|
value={data.storefrontSearchPosition}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
|
@ -123,12 +164,9 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
name={"visibleInStorefront" as keyof FormData}
|
name={"visibleInStorefront" as keyof FormData}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<FormattedMessage
|
<FormattedMessage {...messages.visibleInStorefront} />
|
||||||
defaultMessage="Public"
|
|
||||||
description="attribute visibility in storefront"
|
|
||||||
/>
|
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
<FormattedMessage defaultMessage="If enabled, attribute will be accessible to customers." />
|
<FormattedMessage {...messages.visibleInStorefrontCaption} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -136,14 +174,11 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
{data.inputType !== AttributeInputTypeEnum.FILE && (
|
{dashboardProperties && (
|
||||||
<>
|
<>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
<FormattedMessage
|
<FormattedMessage {...messages.dashboardPropertiesTitle} />
|
||||||
defaultMessage="Dashboard Properties"
|
|
||||||
description="attribute properties regarding dashboard"
|
|
||||||
/>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Hr />
|
<Hr />
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
|
@ -151,12 +186,11 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
name={"filterableInDashboard" as keyof FormData}
|
name={"filterableInDashboard" as keyof FormData}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<FormattedMessage
|
<FormattedMessage {...messages.filterableInDashboard} />
|
||||||
defaultMessage="Use in Filtering"
|
|
||||||
description="use attribute in filtering"
|
|
||||||
/>
|
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
<FormattedMessage defaultMessage="If enabled, you’ll be able to use this attribute to filter products in product list." />
|
<FormattedMessage
|
||||||
|
{...messages.filterableInDashboardCaption}
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -169,12 +203,9 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
|
||||||
name={"availableInGrid" as keyof FormData}
|
name={"availableInGrid" as keyof FormData}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<FormattedMessage
|
<FormattedMessage {...messages.availableInGrid} />
|
||||||
defaultMessage="Add to Column Options"
|
|
||||||
description="add attribute as column in product list table"
|
|
||||||
/>
|
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
<FormattedMessage defaultMessage="If enabled this attribute can be used as a column in product table." />
|
<FormattedMessage {...messages.availableInGridCaption} />
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { AttributeList_attributes_edges_node } from "./types/AttributeList";
|
||||||
export const attribute: AttributeDetailsFragment = {
|
export const attribute: AttributeDetailsFragment = {
|
||||||
__typename: "Attribute" as "Attribute",
|
__typename: "Attribute" as "Attribute",
|
||||||
availableInGrid: true,
|
availableInGrid: true,
|
||||||
|
entityType: null,
|
||||||
filterableInDashboard: false,
|
filterableInDashboard: false,
|
||||||
filterableInStorefront: true,
|
filterableInStorefront: true,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZTo5",
|
id: "UHJvZHVjdEF0dHJpYnV0ZTo5",
|
||||||
|
@ -33,6 +34,7 @@ export const attribute: AttributeDetailsFragment = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI0",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI0",
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
|
reference: null,
|
||||||
slug: "john-doe"
|
slug: "john-doe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -40,6 +42,7 @@ export const attribute: AttributeDetailsFragment = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI1",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI1",
|
||||||
name: "Milionare Pirate",
|
name: "Milionare Pirate",
|
||||||
|
reference: null,
|
||||||
slug: "milionare-pirate"
|
slug: "milionare-pirate"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -63,6 +66,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI0",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI0",
|
||||||
name: "John Doe",
|
name: "John Doe",
|
||||||
|
reference: null,
|
||||||
slug: "john-doe",
|
slug: "john-doe",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -72,6 +76,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI1",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI1",
|
||||||
name: "Milionare Pirate",
|
name: "Milionare Pirate",
|
||||||
|
reference: null,
|
||||||
slug: "milionare-pirate",
|
slug: "milionare-pirate",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -95,6 +100,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE1",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE1",
|
||||||
name: "100g",
|
name: "100g",
|
||||||
|
reference: null,
|
||||||
slug: "100g",
|
slug: "100g",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -104,6 +110,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE2",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE2",
|
||||||
name: "250g",
|
name: "250g",
|
||||||
|
reference: null,
|
||||||
slug: "250g",
|
slug: "250g",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -113,6 +120,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE3",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE3",
|
||||||
name: "500g",
|
name: "500g",
|
||||||
|
reference: null,
|
||||||
slug: "500g",
|
slug: "500g",
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -122,6 +130,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE4",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE4",
|
||||||
name: "1kg",
|
name: "1kg",
|
||||||
|
reference: null,
|
||||||
slug: "1kg",
|
slug: "1kg",
|
||||||
sortOrder: 3,
|
sortOrder: 3,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -145,6 +154,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjY=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjY=",
|
||||||
name: "Saleor",
|
name: "Saleor",
|
||||||
|
reference: null,
|
||||||
slug: "saleor",
|
slug: "saleor",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -168,6 +178,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIx",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIx",
|
||||||
name: "100g",
|
name: "100g",
|
||||||
|
reference: null,
|
||||||
slug: "100g",
|
slug: "100g",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -177,6 +188,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIy",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIy",
|
||||||
name: "250g",
|
name: "250g",
|
||||||
|
reference: null,
|
||||||
slug: "250g",
|
slug: "250g",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -186,6 +198,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIz",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIz",
|
||||||
name: "500g",
|
name: "500g",
|
||||||
|
reference: null,
|
||||||
slug: "500g",
|
slug: "500g",
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -209,6 +222,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEz",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEz",
|
||||||
name: "Arabica",
|
name: "Arabica",
|
||||||
|
reference: null,
|
||||||
slug: "arabica",
|
slug: "arabica",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -218,6 +232,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE0",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE0",
|
||||||
name: "Robusta",
|
name: "Robusta",
|
||||||
|
reference: null,
|
||||||
slug: "robusta",
|
slug: "robusta",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -241,6 +256,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjM=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjM=",
|
||||||
name: "Round",
|
name: "Round",
|
||||||
|
reference: null,
|
||||||
slug: "round",
|
slug: "round",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -250,6 +266,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjQ=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjQ=",
|
||||||
name: "V-Neck",
|
name: "V-Neck",
|
||||||
|
reference: null,
|
||||||
slug: "v-neck",
|
slug: "v-neck",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -259,6 +276,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjU=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjU=",
|
||||||
name: "Polo",
|
name: "Polo",
|
||||||
|
reference: null,
|
||||||
slug: "polo",
|
slug: "polo",
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -282,6 +300,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE=",
|
||||||
name: "Blue",
|
name: "Blue",
|
||||||
|
reference: null,
|
||||||
slug: "blue",
|
slug: "blue",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -291,6 +310,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI=",
|
||||||
name: "White",
|
name: "White",
|
||||||
|
reference: null,
|
||||||
slug: "white",
|
slug: "white",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -314,6 +334,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMw",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMw",
|
||||||
name: "Soft",
|
name: "Soft",
|
||||||
|
reference: null,
|
||||||
slug: "soft",
|
slug: "soft",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -323,6 +344,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMx",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMx",
|
||||||
name: "Hard",
|
name: "Hard",
|
||||||
|
reference: null,
|
||||||
slug: "hard",
|
slug: "hard",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -332,6 +354,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMy",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMy",
|
||||||
name: "Middle soft",
|
name: "Middle soft",
|
||||||
|
reference: null,
|
||||||
slug: "middle-soft",
|
slug: "middle-soft",
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -341,6 +364,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMz",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjMz",
|
||||||
name: "Middle hard",
|
name: "Middle hard",
|
||||||
|
reference: null,
|
||||||
slug: "middle-hard",
|
slug: "middle-hard",
|
||||||
sortOrder: 3,
|
sortOrder: 3,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -350,6 +374,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjM0",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjM0",
|
||||||
name: "Middle",
|
name: "Middle",
|
||||||
|
reference: null,
|
||||||
slug: "middle",
|
slug: "middle",
|
||||||
sortOrder: 4,
|
sortOrder: 4,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -359,6 +384,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjM1",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjM1",
|
||||||
name: "Very hard",
|
name: "Very hard",
|
||||||
|
reference: null,
|
||||||
slug: "very-hard",
|
slug: "very-hard",
|
||||||
sortOrder: 5,
|
sortOrder: 5,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -382,6 +408,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE5",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjE5",
|
||||||
name: "Sour",
|
name: "Sour",
|
||||||
|
reference: null,
|
||||||
slug: "sour",
|
slug: "sour",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -391,6 +418,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIw",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjIw",
|
||||||
name: "Sweet",
|
name: "Sweet",
|
||||||
|
reference: null,
|
||||||
slug: "sweet",
|
slug: "sweet",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -414,6 +442,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI4",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI4",
|
||||||
name: "English",
|
name: "English",
|
||||||
|
reference: null,
|
||||||
slug: "english",
|
slug: "english",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -423,6 +452,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI5",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI5",
|
||||||
name: "Pirate",
|
name: "Pirate",
|
||||||
|
reference: null,
|
||||||
slug: "pirate",
|
slug: "pirate",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -446,6 +476,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI2",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI2",
|
||||||
name: "Mirumee Press",
|
name: "Mirumee Press",
|
||||||
|
reference: null,
|
||||||
slug: "mirumee-press",
|
slug: "mirumee-press",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -455,6 +486,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI3",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjI3",
|
||||||
name: "Saleor Publishing",
|
name: "Saleor Publishing",
|
||||||
|
reference: null,
|
||||||
slug: "saleor-publishing",
|
slug: "saleor-publishing",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -478,6 +510,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjc=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjc=",
|
||||||
name: "XS",
|
name: "XS",
|
||||||
|
reference: null,
|
||||||
slug: "xs",
|
slug: "xs",
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -487,6 +520,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjg=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjg=",
|
||||||
name: "S",
|
name: "S",
|
||||||
|
reference: null,
|
||||||
slug: "s",
|
slug: "s",
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -496,6 +530,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjk=",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjk=",
|
||||||
name: "M",
|
name: "M",
|
||||||
|
reference: null,
|
||||||
slug: "m",
|
slug: "m",
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -505,6 +540,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEw",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEw",
|
||||||
name: "L",
|
name: "L",
|
||||||
|
reference: null,
|
||||||
slug: "l",
|
slug: "l",
|
||||||
sortOrder: 3,
|
sortOrder: 3,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -514,6 +550,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEx",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEx",
|
||||||
name: "XL",
|
name: "XL",
|
||||||
|
reference: null,
|
||||||
slug: "xl",
|
slug: "xl",
|
||||||
sortOrder: 4,
|
sortOrder: 4,
|
||||||
value: ""
|
value: ""
|
||||||
|
@ -523,6 +560,7 @@ export const attributes: Array<AttributeList_attributes_edges_node &
|
||||||
file: null,
|
file: null,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEy",
|
id: "UHJvZHVjdEF0dHJpYnV0ZVZhbHVlOjEy",
|
||||||
name: "XXL",
|
name: "XXL",
|
||||||
|
reference: null,
|
||||||
slug: "xxl",
|
slug: "xxl",
|
||||||
sortOrder: 5,
|
sortOrder: 5,
|
||||||
value: ""
|
value: ""
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { AttributeCreateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
import { AttributeCreateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeEntityTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: AttributeCreate
|
// GraphQL mutation operation: AttributeCreate
|
||||||
|
@ -32,6 +32,7 @@ export interface AttributeCreate_attributeCreate_attribute_values {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
file: AttributeCreate_attributeCreate_attribute_values_file | null;
|
file: AttributeCreate_attributeCreate_attribute_values_file | null;
|
||||||
|
reference: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeCreate_attributeCreate_attribute {
|
export interface AttributeCreate_attributeCreate_attribute {
|
||||||
|
@ -47,6 +48,7 @@ export interface AttributeCreate_attributeCreate_attribute {
|
||||||
privateMetadata: (AttributeCreate_attributeCreate_attribute_privateMetadata | null)[];
|
privateMetadata: (AttributeCreate_attributeCreate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
|
entityType: AttributeEntityTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (AttributeCreate_attributeCreate_attribute_values | null)[] | null;
|
values: (AttributeCreate_attributeCreate_attribute_values | null)[] | null;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { AttributeTypeEnum, AttributeInputTypeEnum } from "./../../types/globalTypes";
|
import { AttributeTypeEnum, AttributeInputTypeEnum, AttributeEntityTypeEnum } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL query operation: AttributeDetails
|
// GraphQL query operation: AttributeDetails
|
||||||
|
@ -32,6 +32,7 @@ export interface AttributeDetails_attribute_values {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
file: AttributeDetails_attribute_values_file | null;
|
file: AttributeDetails_attribute_values_file | null;
|
||||||
|
reference: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeDetails_attribute {
|
export interface AttributeDetails_attribute {
|
||||||
|
@ -47,6 +48,7 @@ export interface AttributeDetails_attribute {
|
||||||
privateMetadata: (AttributeDetails_attribute_privateMetadata | null)[];
|
privateMetadata: (AttributeDetails_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
|
entityType: AttributeEntityTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (AttributeDetails_attribute_values | null)[] | null;
|
values: (AttributeDetails_attribute_values | null)[] | null;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { AttributeUpdateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
import { AttributeUpdateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeEntityTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: AttributeUpdate
|
// GraphQL mutation operation: AttributeUpdate
|
||||||
|
@ -32,6 +32,7 @@ export interface AttributeUpdate_attributeUpdate_attribute_values {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
file: AttributeUpdate_attributeUpdate_attribute_values_file | null;
|
file: AttributeUpdate_attributeUpdate_attribute_values_file | null;
|
||||||
|
reference: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeUpdate_attributeUpdate_attribute {
|
export interface AttributeUpdate_attributeUpdate_attribute {
|
||||||
|
@ -47,6 +48,7 @@ export interface AttributeUpdate_attributeUpdate_attribute {
|
||||||
privateMetadata: (AttributeUpdate_attributeUpdate_attribute_privateMetadata | null)[];
|
privateMetadata: (AttributeUpdate_attributeUpdate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
|
entityType: AttributeEntityTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (AttributeUpdate_attributeUpdate_attribute_values | null)[] | null;
|
values: (AttributeUpdate_attributeUpdate_attribute_values | null)[] | null;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { AttributeValueCreateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
import { AttributeValueCreateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeEntityTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: AttributeValueCreate
|
// GraphQL mutation operation: AttributeValueCreate
|
||||||
|
@ -32,6 +32,7 @@ export interface AttributeValueCreate_attributeValueCreate_attribute_values {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
file: AttributeValueCreate_attributeValueCreate_attribute_values_file | null;
|
file: AttributeValueCreate_attributeValueCreate_attribute_values_file | null;
|
||||||
|
reference: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeValueCreate_attributeValueCreate_attribute {
|
export interface AttributeValueCreate_attributeValueCreate_attribute {
|
||||||
|
@ -47,6 +48,7 @@ export interface AttributeValueCreate_attributeValueCreate_attribute {
|
||||||
privateMetadata: (AttributeValueCreate_attributeValueCreate_attribute_privateMetadata | null)[];
|
privateMetadata: (AttributeValueCreate_attributeValueCreate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
|
entityType: AttributeEntityTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (AttributeValueCreate_attributeValueCreate_attribute_values | null)[] | null;
|
values: (AttributeValueCreate_attributeValueCreate_attribute_values | null)[] | null;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { AttributeTypeEnum, AttributeInputTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
import { AttributeTypeEnum, AttributeInputTypeEnum, AttributeEntityTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: AttributeValueDelete
|
// GraphQL mutation operation: AttributeValueDelete
|
||||||
|
@ -32,6 +32,7 @@ export interface AttributeValueDelete_attributeValueDelete_attribute_values {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
file: AttributeValueDelete_attributeValueDelete_attribute_values_file | null;
|
file: AttributeValueDelete_attributeValueDelete_attribute_values_file | null;
|
||||||
|
reference: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeValueDelete_attributeValueDelete_attribute {
|
export interface AttributeValueDelete_attributeValueDelete_attribute {
|
||||||
|
@ -47,6 +48,7 @@ export interface AttributeValueDelete_attributeValueDelete_attribute {
|
||||||
privateMetadata: (AttributeValueDelete_attributeValueDelete_attribute_privateMetadata | null)[];
|
privateMetadata: (AttributeValueDelete_attributeValueDelete_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
|
entityType: AttributeEntityTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (AttributeValueDelete_attributeValueDelete_attribute_values | null)[] | null;
|
values: (AttributeValueDelete_attributeValueDelete_attribute_values | null)[] | null;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { AttributeValueCreateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
import { AttributeValueCreateInput, AttributeTypeEnum, AttributeInputTypeEnum, AttributeEntityTypeEnum, AttributeErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL mutation operation: AttributeValueUpdate
|
// GraphQL mutation operation: AttributeValueUpdate
|
||||||
|
@ -32,6 +32,7 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute_values {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
slug: string | null;
|
slug: string | null;
|
||||||
file: AttributeValueUpdate_attributeValueUpdate_attribute_values_file | null;
|
file: AttributeValueUpdate_attributeValueUpdate_attribute_values_file | null;
|
||||||
|
reference: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeValueUpdate_attributeValueUpdate_attribute {
|
export interface AttributeValueUpdate_attributeValueUpdate_attribute {
|
||||||
|
@ -47,6 +48,7 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute {
|
||||||
privateMetadata: (AttributeValueUpdate_attributeValueUpdate_attribute_privateMetadata | null)[];
|
privateMetadata: (AttributeValueUpdate_attributeValueUpdate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
|
entityType: AttributeEntityTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
valueRequired: boolean;
|
valueRequired: boolean;
|
||||||
values: (AttributeValueUpdate_attributeValueUpdate_attribute_values | null)[] | null;
|
values: (AttributeValueUpdate_attributeValueUpdate_attribute_values | null)[] | null;
|
||||||
|
|
|
@ -1,17 +1,88 @@
|
||||||
|
import {
|
||||||
|
AttributeInput,
|
||||||
|
AttributeInputData
|
||||||
|
} from "@saleor/components/Attributes";
|
||||||
import { FileUpload } from "@saleor/files/types/FileUpload";
|
import { FileUpload } from "@saleor/files/types/FileUpload";
|
||||||
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
||||||
import { SelectedVariantAttributeFragment } from "@saleor/fragments/types/SelectedVariantAttributeFragment";
|
import { SelectedVariantAttributeFragment } from "@saleor/fragments/types/SelectedVariantAttributeFragment";
|
||||||
import { UploadErrorFragment } from "@saleor/fragments/types/UploadErrorFragment";
|
import { UploadErrorFragment } from "@saleor/fragments/types/UploadErrorFragment";
|
||||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||||
import { PageDetails_page_attributes } from "@saleor/pages/types/PageDetails";
|
import { PageDetails_page_attributes } from "@saleor/pages/types/PageDetails";
|
||||||
|
import { ProductDetails_product_attributes } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
|
||||||
|
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
|
||||||
import {
|
import {
|
||||||
|
AttributeEntityTypeEnum,
|
||||||
AttributeInputTypeEnum,
|
AttributeInputTypeEnum,
|
||||||
AttributeValueInput
|
AttributeValueInput
|
||||||
} from "@saleor/types/globalTypes";
|
} from "@saleor/types/globalTypes";
|
||||||
|
import { mapNodeToChoice, mapPagesToChoices } from "@saleor/utils/maps";
|
||||||
import { MutationFetchResult } from "react-apollo";
|
import { MutationFetchResult } from "react-apollo";
|
||||||
|
|
||||||
|
import { AttributePageFormData } from "../components/AttributePage";
|
||||||
|
import { AttributeValueEditDialogFormData } from "../components/AttributeValueEditDialog";
|
||||||
import { AttributeValueDelete } from "../types/AttributeValueDelete";
|
import { AttributeValueDelete } from "../types/AttributeValueDelete";
|
||||||
|
|
||||||
|
export const ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES = [
|
||||||
|
AttributeInputTypeEnum.DROPDOWN,
|
||||||
|
AttributeInputTypeEnum.MULTISELECT
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface AttributeReference {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSimpleAttributeData(
|
||||||
|
data: AttributePageFormData,
|
||||||
|
values: AttributeValueEditDialogFormData[]
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
metadata: undefined,
|
||||||
|
privateMetadata: undefined,
|
||||||
|
storefrontSearchPosition: parseInt(data.storefrontSearchPosition, 10),
|
||||||
|
values: values.map(value => ({
|
||||||
|
name: value.name
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileOrReferenceAttributeData(
|
||||||
|
data: AttributePageFormData,
|
||||||
|
values: AttributeValueEditDialogFormData[]
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...getSimpleAttributeData(data, values),
|
||||||
|
availableInGrid: undefined,
|
||||||
|
filterableInDashboard: undefined,
|
||||||
|
filterableInStorefront: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAttributeData(
|
||||||
|
data: AttributePageFormData,
|
||||||
|
values: AttributeValueEditDialogFormData[]
|
||||||
|
) {
|
||||||
|
if (ATTRIBUTE_TYPES_WITH_DEDICATED_VALUES.includes(data.inputType)) {
|
||||||
|
return getSimpleAttributeData(data, values);
|
||||||
|
} else {
|
||||||
|
return getFileOrReferenceAttributeData(data, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSelectedAttributeValues(
|
||||||
|
attribute:
|
||||||
|
| PageDetails_page_attributes
|
||||||
|
| ProductDetails_product_attributes
|
||||||
|
| SelectedVariantAttributeFragment
|
||||||
|
) {
|
||||||
|
if (attribute.attribute.inputType === AttributeInputTypeEnum.REFERENCE) {
|
||||||
|
return attribute.values.map(value => value.reference);
|
||||||
|
}
|
||||||
|
return attribute.values.map(value => value.slug);
|
||||||
|
}
|
||||||
|
|
||||||
export const isFileValueUnused = (
|
export const isFileValueUnused = (
|
||||||
attributesWithNewFileValue: FormsetData<null, File>,
|
attributesWithNewFileValue: FormsetData<null, File>,
|
||||||
existingAttribute:
|
existingAttribute:
|
||||||
|
@ -54,6 +125,18 @@ export const mergeAttributeValueDeleteErrors = (
|
||||||
return errors;
|
return errors;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
export const mergeAttributeValues = (
|
||||||
|
attributeId: string,
|
||||||
|
attributeValues: string[],
|
||||||
|
attributes: FormsetData<AttributeInputData, string[]>
|
||||||
|
) => {
|
||||||
|
const attribute = attributes.find(attribute => attribute.id === attributeId);
|
||||||
|
|
||||||
|
return attribute.value
|
||||||
|
? [...attribute.value, ...attributeValues]
|
||||||
|
: attributeValues;
|
||||||
|
};
|
||||||
|
|
||||||
export const getFileValuesToUploadFromAttributes = (
|
export const getFileValuesToUploadFromAttributes = (
|
||||||
attributesWithNewFileValue: FormsetData<null, File>
|
attributesWithNewFileValue: FormsetData<null, File>
|
||||||
) => attributesWithNewFileValue.filter(fileAttribute => !!fileAttribute.value);
|
) => attributesWithNewFileValue.filter(fileAttribute => !!fileAttribute.value);
|
||||||
|
@ -104,3 +187,131 @@ export const getAttributesAfterFileAttributesUpdate = (
|
||||||
|
|
||||||
return uploadedFileAttributes.concat(removedFileAttributes);
|
return uploadedFileAttributes.concat(removedFileAttributes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getFileAttributeDisplayData = (
|
||||||
|
attribute: AttributeInput,
|
||||||
|
attributesWithNewFileValue: FormsetData<null, File>
|
||||||
|
) => {
|
||||||
|
const attributeWithNewFileValue = attributesWithNewFileValue.find(
|
||||||
|
attributeWithNewFile => attribute.id === attributeWithNewFile.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (attributeWithNewFileValue) {
|
||||||
|
return {
|
||||||
|
...attribute,
|
||||||
|
value: attributeWithNewFileValue?.value?.name
|
||||||
|
? [attributeWithNewFileValue.value.name]
|
||||||
|
: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return attribute;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPageReferenceAttributeDisplayData = (
|
||||||
|
attribute: AttributeInput,
|
||||||
|
referencePages: SearchPages_search_edges_node[]
|
||||||
|
) => ({
|
||||||
|
...attribute,
|
||||||
|
data: {
|
||||||
|
...attribute.data,
|
||||||
|
references:
|
||||||
|
referencePages?.length > 0 && attribute.value?.length > 0
|
||||||
|
? mapPagesToChoices(
|
||||||
|
attribute.value.map(value => {
|
||||||
|
const reference = referencePages.find(
|
||||||
|
reference => reference.id === value
|
||||||
|
);
|
||||||
|
return { ...reference };
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getProductReferenceAttributeDisplayData = (
|
||||||
|
attribute: AttributeInput,
|
||||||
|
referenceProducts: SearchProducts_search_edges_node[]
|
||||||
|
) => ({
|
||||||
|
...attribute,
|
||||||
|
data: {
|
||||||
|
...attribute.data,
|
||||||
|
references:
|
||||||
|
referenceProducts?.length > 0 && attribute.value?.length > 0
|
||||||
|
? mapNodeToChoice(
|
||||||
|
attribute.value.map(value => {
|
||||||
|
const reference = referenceProducts.find(
|
||||||
|
reference => reference.id === value
|
||||||
|
);
|
||||||
|
return { ...reference };
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getReferenceAttributeDisplayData = (
|
||||||
|
attribute: AttributeInput,
|
||||||
|
referencePages: SearchPages_search_edges_node[],
|
||||||
|
referenceProducts: SearchProducts_search_edges_node[]
|
||||||
|
) => {
|
||||||
|
if (attribute.data.entityType === AttributeEntityTypeEnum.PAGE) {
|
||||||
|
return getPageReferenceAttributeDisplayData(attribute, referencePages);
|
||||||
|
} else if (attribute.data.entityType === AttributeEntityTypeEnum.PRODUCT) {
|
||||||
|
return getProductReferenceAttributeDisplayData(
|
||||||
|
attribute,
|
||||||
|
referenceProducts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAttributesDisplayData = (
|
||||||
|
attributes: AttributeInput[],
|
||||||
|
attributesWithNewFileValue: FormsetData<null, File>,
|
||||||
|
referencePages: SearchPages_search_edges_node[],
|
||||||
|
referenceProducts: SearchProducts_search_edges_node[]
|
||||||
|
) =>
|
||||||
|
attributes.map(attribute => {
|
||||||
|
if (attribute.data.inputType === AttributeInputTypeEnum.REFERENCE) {
|
||||||
|
return getReferenceAttributeDisplayData(
|
||||||
|
attribute,
|
||||||
|
referencePages,
|
||||||
|
referenceProducts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (attribute.data.inputType === AttributeInputTypeEnum.FILE) {
|
||||||
|
return getFileAttributeDisplayData(attribute, attributesWithNewFileValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attribute;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getSelectedReferencesFromAttribute = <
|
||||||
|
Node extends SearchPages_search_edges_node | SearchProducts_search_edges_node
|
||||||
|
>(
|
||||||
|
attribute?: AttributeInput,
|
||||||
|
references?: Node[]
|
||||||
|
) =>
|
||||||
|
references?.filter(
|
||||||
|
value =>
|
||||||
|
!attribute?.value?.some(selectedValue => selectedValue === value.id)
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
export const getAttributeValuesFromReferences = (
|
||||||
|
attributeId: string,
|
||||||
|
attributes?: AttributeInput[],
|
||||||
|
referencePages?: SearchPages_search_edges_node[],
|
||||||
|
referenceProducts?: SearchProducts_search_edges_node[]
|
||||||
|
) => {
|
||||||
|
const attribute = attributes?.find(attribute => attribute.id === attributeId);
|
||||||
|
|
||||||
|
if (attribute?.data?.entityType === AttributeEntityTypeEnum.PAGE) {
|
||||||
|
return mapPagesToChoices(
|
||||||
|
getSelectedReferencesFromAttribute(attribute, referencePages)
|
||||||
|
);
|
||||||
|
} else if (attribute?.data?.entityType === AttributeEntityTypeEnum.PRODUCT) {
|
||||||
|
return mapNodeToChoice(
|
||||||
|
getSelectedReferencesFromAttribute(attribute, referenceProducts)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
import { createAttributeMultiChangeHandler } from "@saleor/attributes/utils/handlers";
|
||||||
import { AttributeInputData } from "@saleor/components/Attributes";
|
import { AttributeInputData } from "@saleor/components/Attributes";
|
||||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
import { FormsetData } from "@saleor/hooks/useFormset";
|
||||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
||||||
|
|
||||||
import { createAttributeMultiChangeHandler } from "./handlers";
|
|
||||||
|
|
||||||
const attributes: FormsetData<AttributeInputData, string[]> = [
|
const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
|
@ -15,6 +14,7 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
file: null,
|
file: null,
|
||||||
id: "attrv-1",
|
id: "attrv-1",
|
||||||
name: "Attribute 1 Value 1",
|
name: "Attribute 1 Value 1",
|
||||||
|
reference: null,
|
||||||
slug: "attr-1-v-1"
|
slug: "attr-1-v-1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -33,6 +33,7 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
file: null,
|
file: null,
|
||||||
id: "attrv-2",
|
id: "attrv-2",
|
||||||
name: "Attribute 2 Value 1",
|
name: "Attribute 2 Value 1",
|
||||||
|
reference: null,
|
||||||
slug: "attr-2-v-1"
|
slug: "attr-2-v-1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -40,6 +41,7 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
file: null,
|
file: null,
|
||||||
id: "attrv-3",
|
id: "attrv-3",
|
||||||
name: "Attribute 2 Value 2",
|
name: "Attribute 2 Value 2",
|
||||||
|
reference: null,
|
||||||
slug: "attr-2-v-2"
|
slug: "attr-2-v-2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -47,6 +49,7 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
file: null,
|
file: null,
|
||||||
id: "attrv-4",
|
id: "attrv-4",
|
||||||
name: "Attribute 2 Value 3",
|
name: "Attribute 2 Value 3",
|
||||||
|
reference: null,
|
||||||
slug: "attr-2-v-3"
|
slug: "attr-2-v-3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -69,6 +72,7 @@ const attributes: FormsetData<AttributeInputData, string[]> = [
|
||||||
},
|
},
|
||||||
id: "gdghdgdhkkdae",
|
id: "gdghdgdhkkdae",
|
||||||
name: "File First Value",
|
name: "File First Value",
|
||||||
|
reference: null,
|
||||||
slug: "file-first-value"
|
slug: "file-first-value"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -1,14 +1,24 @@
|
||||||
import { AttributeInput } from "@saleor/components/Attributes";
|
import {
|
||||||
|
AttributeInput,
|
||||||
|
AttributeInputData
|
||||||
|
} from "@saleor/components/Attributes";
|
||||||
import {
|
import {
|
||||||
FileUpload,
|
FileUpload,
|
||||||
FileUploadVariables
|
FileUploadVariables
|
||||||
} from "@saleor/files/types/FileUpload";
|
} from "@saleor/files/types/FileUpload";
|
||||||
import { FormsetData } from "@saleor/hooks/useFormset";
|
|
||||||
import { PageDetails_page_attributes } from "@saleor/pages/types/PageDetails";
|
|
||||||
import {
|
import {
|
||||||
|
FormsetAtomicData,
|
||||||
|
FormsetChange,
|
||||||
|
FormsetData
|
||||||
|
} from "@saleor/hooks/useFormset";
|
||||||
|
import { PageDetails_page_attributes } from "@saleor/pages/types/PageDetails";
|
||||||
|
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
|
||||||
|
import {
|
||||||
|
AttributeEntityTypeEnum,
|
||||||
AttributeInputTypeEnum,
|
AttributeInputTypeEnum,
|
||||||
AttributeValueInput
|
AttributeValueInput
|
||||||
} from "@saleor/types/globalTypes";
|
} from "@saleor/types/globalTypes";
|
||||||
|
import { move, toggle } from "@saleor/utils/lists";
|
||||||
import { MutationFetchResult } from "react-apollo";
|
import { MutationFetchResult } from "react-apollo";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -17,6 +27,149 @@ import {
|
||||||
} from "../types/AttributeValueDelete";
|
} from "../types/AttributeValueDelete";
|
||||||
import { getFileValuesToUploadFromAttributes, isFileValueUnused } from "./data";
|
import { getFileValuesToUploadFromAttributes, isFileValueUnused } from "./data";
|
||||||
|
|
||||||
|
export function createAttributeChangeHandler(
|
||||||
|
changeAttributeData: FormsetChange<string[]>,
|
||||||
|
triggerChange: () => void
|
||||||
|
): FormsetChange<string> {
|
||||||
|
return (attributeId: string, value: string) => {
|
||||||
|
triggerChange();
|
||||||
|
changeAttributeData(attributeId, value === "" ? [] : [value]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAttributeMultiChangeHandler(
|
||||||
|
changeAttributeData: FormsetChange<string[]>,
|
||||||
|
attributes: FormsetData<AttributeInputData, string[]>,
|
||||||
|
triggerChange: () => void
|
||||||
|
): FormsetChange<string> {
|
||||||
|
return (attributeId: string, value: string) => {
|
||||||
|
const attribute = attributes.find(
|
||||||
|
attribute => attribute.id === attributeId
|
||||||
|
);
|
||||||
|
|
||||||
|
const newAttributeValues = toggle(
|
||||||
|
value,
|
||||||
|
attribute.value,
|
||||||
|
(a, b) => a === b
|
||||||
|
);
|
||||||
|
|
||||||
|
triggerChange();
|
||||||
|
changeAttributeData(attributeId, newAttributeValues);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAttributeReferenceChangeHandler(
|
||||||
|
changeAttributeData: FormsetChange<string[]>,
|
||||||
|
triggerChange: () => void
|
||||||
|
): FormsetChange<string[]> {
|
||||||
|
return (attributeId: string, values: string[]) => {
|
||||||
|
changeAttributeData(attributeId, values);
|
||||||
|
triggerChange();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFetchReferencesHandler(
|
||||||
|
attributes: FormsetData<AttributeInputData, string[]>,
|
||||||
|
assignReferencesAttributeId: string,
|
||||||
|
fetchReferencePages?: (data: string) => void,
|
||||||
|
fetchReferenceProducts?: (data: string) => void
|
||||||
|
) {
|
||||||
|
return (value: string) => {
|
||||||
|
const attribute = attributes?.find(
|
||||||
|
attribute => attribute.id === assignReferencesAttributeId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!attribute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
attribute.data.entityType === AttributeEntityTypeEnum.PAGE &&
|
||||||
|
fetchReferencePages
|
||||||
|
) {
|
||||||
|
fetchReferencePages(value);
|
||||||
|
} else if (
|
||||||
|
attribute.data.entityType === AttributeEntityTypeEnum.PRODUCT &&
|
||||||
|
fetchReferenceProducts
|
||||||
|
) {
|
||||||
|
fetchReferenceProducts(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFetchMoreReferencesHandler(
|
||||||
|
attributes: FormsetData<AttributeInputData, string[]>,
|
||||||
|
assignReferencesAttributeId: string,
|
||||||
|
fetchMoreReferencePages?: FetchMoreProps,
|
||||||
|
fetchMoreReferenceProducts?: FetchMoreProps
|
||||||
|
) {
|
||||||
|
const attribute = attributes?.find(
|
||||||
|
attribute => attribute.id === assignReferencesAttributeId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!attribute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.data.entityType === AttributeEntityTypeEnum.PAGE) {
|
||||||
|
return fetchMoreReferencePages;
|
||||||
|
} else if (attribute.data.entityType === AttributeEntityTypeEnum.PRODUCT) {
|
||||||
|
return fetchMoreReferenceProducts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAttributeFileChangeHandler(
|
||||||
|
changeAttributeData: FormsetChange<string[]>,
|
||||||
|
attributesWithNewFileValue: FormsetData<FormsetData<null, File>>,
|
||||||
|
addAttributeNewFileValue: (data: FormsetAtomicData<null, File>) => void,
|
||||||
|
changeAttributeNewFileValue: FormsetChange<File>,
|
||||||
|
triggerChange: () => void
|
||||||
|
): FormsetChange<File> {
|
||||||
|
return (attributeId: string, value: File) => {
|
||||||
|
triggerChange();
|
||||||
|
|
||||||
|
const newFileValueAssigned = attributesWithNewFileValue.find(
|
||||||
|
attribute => attribute.id === attributeId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newFileValueAssigned) {
|
||||||
|
changeAttributeNewFileValue(attributeId, value);
|
||||||
|
} else {
|
||||||
|
addAttributeNewFileValue({
|
||||||
|
data: null,
|
||||||
|
id: attributeId,
|
||||||
|
label: null,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeAttributeData(attributeId, value ? [value.name] : []);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAttributeValueReorderHandler(
|
||||||
|
changeAttributeData: FormsetChange<string[]>,
|
||||||
|
attributes: FormsetData<AttributeInputData, string[]>,
|
||||||
|
triggerChange: () => void
|
||||||
|
): FormsetChange<ReorderEvent> {
|
||||||
|
return (attributeId: string, reorder: ReorderEvent) => {
|
||||||
|
triggerChange();
|
||||||
|
|
||||||
|
const attribute = attributes.find(
|
||||||
|
attribute => attribute.id === attributeId
|
||||||
|
);
|
||||||
|
|
||||||
|
const reorderedValues = move(
|
||||||
|
attribute.value[reorder.oldIndex],
|
||||||
|
attribute.value,
|
||||||
|
(a, b) => a === b,
|
||||||
|
reorder.newIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
changeAttributeData(attributeId, reorderedValues);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface AttributesArgs {
|
interface AttributesArgs {
|
||||||
attributes: AttributeInput[];
|
attributes: AttributeInput[];
|
||||||
updatedFileAttributes: AttributeValueInput[];
|
updatedFileAttributes: AttributeValueInput[];
|
||||||
|
@ -44,6 +197,12 @@ export const prepareAttributesInput = ({
|
||||||
id: attribute.id
|
id: attribute.id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (attribute.data.inputType === AttributeInputTypeEnum.REFERENCE) {
|
||||||
|
return {
|
||||||
|
id: attribute.id,
|
||||||
|
references: attribute.value
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
id: attribute.id,
|
id: attribute.id,
|
||||||
values: attribute.value[0] === "" ? [] : attribute.value
|
values: attribute.value[0] === "" ? [] : attribute.value
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
|
import { getAttributeData } from "@saleor/attributes/utils/data";
|
||||||
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
import {
|
import { AttributeErrorCode } from "@saleor/types/globalTypes";
|
||||||
AttributeErrorCode,
|
|
||||||
AttributeInputTypeEnum
|
|
||||||
} from "@saleor/types/globalTypes";
|
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
||||||
import {
|
import {
|
||||||
|
@ -57,33 +55,6 @@ function areValuesEqual(
|
||||||
return a.name === b.name;
|
return a.name === b.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSimpleAttributeData(
|
|
||||||
data: AttributePageFormData,
|
|
||||||
values: AttributeValueEditDialogFormData[]
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
metadata: undefined,
|
|
||||||
privateMetadata: undefined,
|
|
||||||
storefrontSearchPosition: parseInt(data.storefrontSearchPosition, 10),
|
|
||||||
values: values.map(value => ({
|
|
||||||
name: value.name
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileAttributeData(
|
|
||||||
data: AttributePageFormData,
|
|
||||||
values: AttributeValueEditDialogFormData[]
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...getSimpleAttributeData(data, values),
|
|
||||||
availableInGrid: undefined,
|
|
||||||
filterableInDashboard: undefined,
|
|
||||||
filterableInStorefront: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
|
@ -145,10 +116,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
setValues(move(values[oldIndex], values, areValuesEqual, newIndex));
|
setValues(move(values[oldIndex], values, areValuesEqual, newIndex));
|
||||||
|
|
||||||
const handleCreate = async (data: AttributePageFormData) => {
|
const handleCreate = async (data: AttributePageFormData) => {
|
||||||
const input =
|
const input = getAttributeData(data, values);
|
||||||
data.inputType === AttributeInputTypeEnum.FILE
|
|
||||||
? getFileAttributeData(data, values)
|
|
||||||
: getSimpleAttributeData(data, values);
|
|
||||||
|
|
||||||
const result = await attributeCreate({
|
const result = await attributeCreate({
|
||||||
variables: {
|
variables: {
|
||||||
|
@ -190,6 +158,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
__typename: "AttributeValue" as "AttributeValue",
|
__typename: "AttributeValue" as "AttributeValue",
|
||||||
file: null,
|
file: null,
|
||||||
id: valueIndex.toString(),
|
id: valueIndex.toString(),
|
||||||
|
reference: null,
|
||||||
slug: slugify(value.name).toLowerCase(),
|
slug: slugify(value.name).toLowerCase(),
|
||||||
sortOrder: valueIndex,
|
sortOrder: valueIndex,
|
||||||
value: null,
|
value: null,
|
||||||
|
|
|
@ -178,6 +178,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
||||||
const handleUpdate = async (data: AttributePageFormData) => {
|
const handleUpdate = async (data: AttributePageFormData) => {
|
||||||
const input = {
|
const input = {
|
||||||
...data,
|
...data,
|
||||||
|
entityType: undefined,
|
||||||
inputType: undefined,
|
inputType: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
privateMetadata: undefined,
|
privateMetadata: undefined,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import setupApi from "@test/api";
|
||||||
import { act, renderHook } from "@testing-library/react-hooks";
|
import { act, renderHook } from "@testing-library/react-hooks";
|
||||||
import ApolloClient from "apollo-client";
|
import ApolloClient from "apollo-client";
|
||||||
|
|
||||||
import { useAuthProvider } from "./AuthProvider";
|
import { useAuthProvider } from "./hooks/useAuthProvider";
|
||||||
import { getTokens, setAuthToken } from "./utils";
|
import { getTokens, setAuthToken } from "./utils";
|
||||||
|
|
||||||
const apolloClient = setupApi();
|
const apolloClient = setupApi();
|
||||||
|
@ -14,18 +14,23 @@ function renderAuthProvider(apolloClient: ApolloClient<any>) {
|
||||||
const notify = jest.fn();
|
const notify = jest.fn();
|
||||||
|
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useAuthProvider(intl as any, notify, apolloClient)
|
useAuthProvider({ apolloClient, intl: intl as any, notify })
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const credentials = {
|
const adminCredentials = {
|
||||||
email: "admin@example.com",
|
email: "admin@example.com",
|
||||||
password: "admin",
|
password: "admin",
|
||||||
token: null
|
token: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nonStaffUserCredentials = {
|
||||||
|
email: "client@example.com",
|
||||||
|
password: "password"
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
sessionStorage.clear();
|
sessionStorage.clear();
|
||||||
|
@ -36,10 +41,10 @@ describe("User", () => {
|
||||||
const hook = renderAuthProvider(apolloClient);
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
await act(() =>
|
await act(() =>
|
||||||
hook.current.login(credentials.email, credentials.password)
|
hook.current.login(adminCredentials.email, adminCredentials.password)
|
||||||
);
|
);
|
||||||
expect(hook.current.userContext.email).toBe(credentials.email);
|
expect(hook.current.user.email).toBe(adminCredentials.email);
|
||||||
credentials.token = getTokens().auth;
|
adminCredentials.token = getTokens().auth;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -48,19 +53,33 @@ describe("User", () => {
|
||||||
const hook = renderAuthProvider(apolloClient);
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
await act(() =>
|
await act(() =>
|
||||||
hook.current.login(credentials.email, "NotAValidPassword123!")
|
hook.current.login(adminCredentials.email, "NotAValidPassword123!")
|
||||||
);
|
);
|
||||||
expect(hook.current.userContext).toBe(null);
|
expect(hook.current.user).toBe(null);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will not be logged in if is non-staff", async done => {
|
||||||
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
|
await act(() =>
|
||||||
|
hook.current.login(
|
||||||
|
nonStaffUserCredentials.email,
|
||||||
|
nonStaffUserCredentials.password
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(hook.current.user).toBe(undefined);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("will be logged if has valid token", async done => {
|
it("will be logged if has valid token", async done => {
|
||||||
setAuthToken(credentials.token, false);
|
setAuthToken(adminCredentials.token, false);
|
||||||
const hook = renderAuthProvider(apolloClient);
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
await act(() => hook.current.autologinPromise.current);
|
await act(() => hook.current.autologinPromise.current);
|
||||||
expect(hook.current.userContext.email).toBe(credentials.email);
|
expect(hook.current.user.email).toBe(adminCredentials.email);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -70,7 +89,7 @@ describe("User", () => {
|
||||||
const hook = renderAuthProvider(apolloClient);
|
const hook = renderAuthProvider(apolloClient);
|
||||||
|
|
||||||
await act(() => hook.current.autologinPromise.current);
|
await act(() => hook.current.autologinPromise.current);
|
||||||
expect(hook.current.userContext).toBe(undefined);
|
expect(hook.current.user).toBe(undefined);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,179 +1,11 @@
|
||||||
import { IMessageContext } from "@saleor/components/messages";
|
|
||||||
import { DEMO_MODE } from "@saleor/config";
|
|
||||||
import { User } from "@saleor/fragments/types/User";
|
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { getMutationStatus } from "@saleor/misc";
|
import React, { useContext } from "react";
|
||||||
import {
|
import { useApolloClient } from "react-apollo";
|
||||||
isSupported as isCredentialsManagementAPISupported,
|
import { useIntl } from "react-intl";
|
||||||
login as loginWithCredentialsManagementAPI,
|
|
||||||
saveCredentials
|
|
||||||
} from "@saleor/utils/credentialsManagement";
|
|
||||||
import ApolloClient from "apollo-client";
|
|
||||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import { useApolloClient, useMutation } from "react-apollo";
|
|
||||||
import { IntlShape, useIntl } from "react-intl";
|
|
||||||
|
|
||||||
import { UserContext } from "./";
|
import { UserContext } from "./";
|
||||||
import {
|
import { useAuthProvider } from "./hooks/useAuthProvider";
|
||||||
tokenAuthMutation,
|
import { getTokens } from "./utils";
|
||||||
tokenRefreshMutation,
|
|
||||||
tokenVerifyMutation
|
|
||||||
} from "./mutations";
|
|
||||||
import { RefreshToken, RefreshTokenVariables } from "./types/RefreshToken";
|
|
||||||
import { TokenAuth, TokenAuthVariables } from "./types/TokenAuth";
|
|
||||||
import { VerifyToken, VerifyTokenVariables } from "./types/VerifyToken";
|
|
||||||
import {
|
|
||||||
displayDemoMessage,
|
|
||||||
getTokens,
|
|
||||||
removeTokens,
|
|
||||||
setAuthToken,
|
|
||||||
setTokens
|
|
||||||
} from "./utils";
|
|
||||||
|
|
||||||
const persistToken = false;
|
|
||||||
|
|
||||||
export function useAuthProvider(
|
|
||||||
intl: IntlShape,
|
|
||||||
notify: IMessageContext,
|
|
||||||
apolloClient: ApolloClient<any>
|
|
||||||
) {
|
|
||||||
const [userContext, setUserContext] = useState<undefined | User>(undefined);
|
|
||||||
const autologinPromise = useRef<Promise<any>>();
|
|
||||||
const refreshPromise = useRef<Promise<boolean>>();
|
|
||||||
|
|
||||||
const logout = () => {
|
|
||||||
setUserContext(undefined);
|
|
||||||
if (isCredentialsManagementAPISupported) {
|
|
||||||
navigator.credentials.preventSilentAccess();
|
|
||||||
}
|
|
||||||
removeTokens();
|
|
||||||
};
|
|
||||||
|
|
||||||
const [tokenAuth, tokenAuthResult] = useMutation<
|
|
||||||
TokenAuth,
|
|
||||||
TokenAuthVariables
|
|
||||||
>(tokenAuthMutation, {
|
|
||||||
client: apolloClient,
|
|
||||||
onCompleted: result => {
|
|
||||||
if (result.tokenCreate.errors.length > 0) {
|
|
||||||
logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = result.tokenCreate.user;
|
|
||||||
|
|
||||||
// FIXME: Now we set state also when auth fails and returned user is
|
|
||||||
// `null`, because the LoginView uses this `null` to display error.
|
|
||||||
setUserContext(user);
|
|
||||||
if (user) {
|
|
||||||
setTokens(
|
|
||||||
result.tokenCreate.token,
|
|
||||||
result.tokenCreate.csrfToken,
|
|
||||||
persistToken
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: logout
|
|
||||||
});
|
|
||||||
const [tokenRefresh] = useMutation<RefreshToken, RefreshTokenVariables>(
|
|
||||||
tokenRefreshMutation,
|
|
||||||
{
|
|
||||||
client: apolloClient,
|
|
||||||
onError: logout
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const [tokenVerify, tokenVerifyResult] = useMutation<
|
|
||||||
VerifyToken,
|
|
||||||
VerifyTokenVariables
|
|
||||||
>(tokenVerifyMutation, {
|
|
||||||
client: apolloClient,
|
|
||||||
onCompleted: result => {
|
|
||||||
if (result.tokenVerify === null) {
|
|
||||||
logout();
|
|
||||||
} else {
|
|
||||||
const user = result.tokenVerify?.user;
|
|
||||||
|
|
||||||
if (!!user) {
|
|
||||||
setUserContext(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: logout
|
|
||||||
});
|
|
||||||
|
|
||||||
const tokenAuthOpts = {
|
|
||||||
...tokenAuthResult,
|
|
||||||
status: getMutationStatus(tokenAuthResult)
|
|
||||||
};
|
|
||||||
const tokenVerifyOpts = {
|
|
||||||
...tokenVerifyResult,
|
|
||||||
status: getMutationStatus(tokenVerifyResult)
|
|
||||||
};
|
|
||||||
|
|
||||||
const onLogin = () => {
|
|
||||||
if (DEMO_MODE) {
|
|
||||||
displayDemoMessage(intl, notify);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const token = getTokens().auth;
|
|
||||||
if (!!token && !userContext) {
|
|
||||||
autologinPromise.current = tokenVerify({ variables: { token } });
|
|
||||||
} else {
|
|
||||||
autologinPromise.current = loginWithCredentialsManagementAPI(login);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const login = async (email: string, password: string) => {
|
|
||||||
const result = await tokenAuth({ variables: { email, password } });
|
|
||||||
|
|
||||||
if (result && !result.data.tokenCreate.errors.length) {
|
|
||||||
if (!!onLogin) {
|
|
||||||
onLogin();
|
|
||||||
}
|
|
||||||
saveCredentials(result.data.tokenCreate.user, password);
|
|
||||||
|
|
||||||
return result.data.tokenCreate.user;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loginByToken = (auth: string, refresh: string, user: User) => {
|
|
||||||
setUserContext(user);
|
|
||||||
setTokens(auth, refresh, persistToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshToken = (): Promise<boolean> => {
|
|
||||||
if (!!refreshPromise.current) {
|
|
||||||
return refreshPromise.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const token = getTokens().refresh;
|
|
||||||
|
|
||||||
return tokenRefresh({ variables: { token } }).then(refreshData => {
|
|
||||||
if (!!refreshData.data.tokenRefresh?.token) {
|
|
||||||
setAuthToken(refreshData.data.tokenRefresh.token, persistToken);
|
|
||||||
return resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolve(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
autologinPromise,
|
|
||||||
login,
|
|
||||||
loginByToken,
|
|
||||||
logout,
|
|
||||||
refreshToken,
|
|
||||||
tokenAuthOpts,
|
|
||||||
tokenVerifyOpts,
|
|
||||||
userContext
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthProviderProps {
|
interface AuthProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -184,30 +16,10 @@ const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
|
|
||||||
const {
|
const authProvider = useAuthProvider({ apolloClient, intl, notify });
|
||||||
login,
|
|
||||||
loginByToken,
|
|
||||||
logout,
|
|
||||||
tokenAuthOpts,
|
|
||||||
refreshToken,
|
|
||||||
tokenVerifyOpts,
|
|
||||||
userContext
|
|
||||||
} = useAuthProvider(intl, notify, apolloClient);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider
|
<UserContext.Provider value={authProvider}>{children}</UserContext.Provider>
|
||||||
value={{
|
|
||||||
login,
|
|
||||||
loginByToken,
|
|
||||||
logout,
|
|
||||||
tokenAuthLoading: tokenAuthOpts.loading,
|
|
||||||
tokenRefresh: refreshToken,
|
|
||||||
tokenVerifyLoading: tokenVerifyOpts.loading,
|
|
||||||
user: userContext
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</UserContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import LoginLoading from "../../../auth/components/LoginLoading";
|
import LoginLoading from ".";
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
|
|
||||||
storiesOf("Views / Authentication / Verifying remembered user", module)
|
storiesOf("Views / Authentication / Verifying remembered user", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
|
@ -1,14 +1,24 @@
|
||||||
import { Omit } from "@material-ui/core";
|
import { Omit } from "@material-ui/core";
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import LoginPage, { LoginCardProps } from "../../../auth/components/LoginPage";
|
import LoginPage, { LoginCardProps } from "../../../auth/components/LoginPage";
|
||||||
import CardDecorator from "../../CardDecorator";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
|
|
||||||
const props: Omit<LoginCardProps, "classes"> = {
|
const props: Omit<LoginCardProps, "classes"> = {
|
||||||
disableLoginButton: true,
|
disabled: false,
|
||||||
error: false,
|
error: false,
|
||||||
|
externalAuthentications: [
|
||||||
|
{
|
||||||
|
__typename: "ExternalAuthentication",
|
||||||
|
id: "auth.plugin.example",
|
||||||
|
name: "Example auth plugin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
externalError: false,
|
||||||
|
loading: false,
|
||||||
|
onExternalAuthentication: () => undefined,
|
||||||
onPasswordRecovery: undefined,
|
onPasswordRecovery: undefined,
|
||||||
onSubmit: () => undefined
|
onSubmit: () => undefined
|
||||||
};
|
};
|
||||||
|
@ -18,4 +28,5 @@ storiesOf("Views / Authentication / Log in", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <LoginPage {...props} />)
|
.add("default", () => <LoginPage {...props} />)
|
||||||
.add("error", () => <LoginPage {...props} error={true} />)
|
.add("error", () => <LoginPage {...props} error={true} />)
|
||||||
.add("loading", () => <LoginPage {...props} disableLoginButton={true} />);
|
.add("disabled", () => <LoginPage {...props} disabled={true} />)
|
||||||
|
.add("loading", () => <LoginPage {...props} loading={true} />);
|
|
@ -1,18 +1,17 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
import Divider from "@material-ui/core/Divider";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import Form from "@saleor/components/Form";
|
import { AvailableExternalAuthentications_shop_availableExternalAuthentications } from "@saleor/auth/types/AvailableExternalAuthentications";
|
||||||
import { FormSpacer } from "@saleor/components/FormSpacer";
|
import { FormSpacer } from "@saleor/components/FormSpacer";
|
||||||
import { DEMO_MODE } from "@saleor/config";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
export interface FormData {
|
import LoginForm, { LoginFormData } from "./form";
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
|
@ -23,7 +22,13 @@ const useStyles = makeStyles(
|
||||||
link: {
|
link: {
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
textAlign: "center"
|
textDecoration: "underline"
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
height: "100vh",
|
||||||
|
justifyContent: "center"
|
||||||
},
|
},
|
||||||
loginButton: {
|
loginButton: {
|
||||||
width: 140
|
width: 140
|
||||||
|
@ -43,27 +48,40 @@ const useStyles = makeStyles(
|
||||||
|
|
||||||
export interface LoginCardProps {
|
export interface LoginCardProps {
|
||||||
error: boolean;
|
error: boolean;
|
||||||
disableLoginButton: boolean;
|
externalError: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
externalAuthentications?: AvailableExternalAuthentications_shop_availableExternalAuthentications[];
|
||||||
|
onExternalAuthentication: (pluginId: string) => void;
|
||||||
onPasswordRecovery: () => void;
|
onPasswordRecovery: () => void;
|
||||||
onSubmit?(event: FormData);
|
onSubmit?: (event: LoginFormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoginCard: React.FC<LoginCardProps> = props => {
|
const LoginCard: React.FC<LoginCardProps> = props => {
|
||||||
const { error, disableLoginButton, onPasswordRecovery, onSubmit } = props;
|
const {
|
||||||
|
error,
|
||||||
|
externalError,
|
||||||
|
disabled,
|
||||||
|
loading,
|
||||||
|
externalAuthentications = [],
|
||||||
|
onExternalAuthentication,
|
||||||
|
onPasswordRecovery,
|
||||||
|
onSubmit
|
||||||
|
} = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
let initialFormData = { email: "", password: "" };
|
if (loading) {
|
||||||
if (DEMO_MODE) {
|
return (
|
||||||
initialFormData = {
|
<div className={classes.loading}>
|
||||||
email: "admin@example.com",
|
<CircularProgress size={128} />
|
||||||
password: "admin"
|
</div>
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialFormData} onSubmit={onSubmit}>
|
<LoginForm onSubmit={onSubmit}>
|
||||||
{({ change: handleChange, data, submit: handleSubmit }) => (
|
{({ change: handleChange, data, submit: handleSubmit }) => (
|
||||||
<>
|
<>
|
||||||
{error && (
|
{error && (
|
||||||
|
@ -73,6 +91,13 @@ const LoginCard: React.FC<LoginCardProps> = props => {
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{externalError && (
|
||||||
|
<div className={classes.panel} data-test="loginErrorMessage">
|
||||||
|
<Typography variant="caption">
|
||||||
|
<FormattedMessage defaultMessage="Sorry, login went wrong. Please try again." />
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
fullWidth
|
fullWidth
|
||||||
|
@ -84,6 +109,7 @@ const LoginCard: React.FC<LoginCardProps> = props => {
|
||||||
inputProps={{
|
inputProps={{
|
||||||
"data-test": "email"
|
"data-test": "email"
|
||||||
}}
|
}}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -99,13 +125,14 @@ const LoginCard: React.FC<LoginCardProps> = props => {
|
||||||
inputProps={{
|
inputProps={{
|
||||||
"data-test": "password"
|
"data-test": "password"
|
||||||
}}
|
}}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<div className={classes.buttonContainer}>
|
<div className={classes.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
className={classes.loginButton}
|
className={classes.loginButton}
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={disableLoginButton}
|
disabled={disabled}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -115,15 +142,56 @@ const LoginCard: React.FC<LoginCardProps> = props => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<Typography className={classes.link} onClick={onPasswordRecovery}>
|
<Typography>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Reset your password"
|
defaultMessage="Forgot password? {resetPasswordLink}"
|
||||||
description="button"
|
description="description"
|
||||||
|
values={{
|
||||||
|
resetPasswordLink: (
|
||||||
|
<a className={classes.link} onClick={onPasswordRecovery}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Use this link to recover it"
|
||||||
|
description="link"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
{externalAuthentications.length > 0 && (
|
||||||
|
<>
|
||||||
|
<FormSpacer />
|
||||||
|
<Divider />
|
||||||
|
<FormSpacer />
|
||||||
|
<Typography>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="or login using"
|
||||||
|
description="description"
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Form>
|
{externalAuthentications.map(externalAuthentication => (
|
||||||
|
<React.Fragment key={externalAuthentication.id}>
|
||||||
|
<FormSpacer />
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
size="large"
|
||||||
|
onClick={() =>
|
||||||
|
onExternalAuthentication(externalAuthentication.id)
|
||||||
|
}
|
||||||
|
data-test="external-authentication"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{externalAuthentication.name}
|
||||||
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</LoginForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
LoginCard.displayName = "LoginCard";
|
LoginCard.displayName = "LoginCard";
|
||||||
|
|
73
src/auth/components/LoginPage/form.tsx
Normal file
73
src/auth/components/LoginPage/form.tsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
|
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface LoginFormData {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseLoginFormResult {
|
||||||
|
change: FormChange;
|
||||||
|
data: LoginFormData;
|
||||||
|
hasChanged: boolean;
|
||||||
|
submit: () => Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginFormProps {
|
||||||
|
children: (props: UseLoginFormResult) => React.ReactNode;
|
||||||
|
onSubmit: (data: LoginFormData) => SubmitPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLoginFormData = () => {
|
||||||
|
if (DEMO_MODE) {
|
||||||
|
return {
|
||||||
|
email: "admin@example.com",
|
||||||
|
password: "admin"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { email: "", password: "" };
|
||||||
|
};
|
||||||
|
|
||||||
|
function useLoginForm(
|
||||||
|
onSubmit: (data: LoginFormData) => SubmitPromise
|
||||||
|
): UseLoginFormResult {
|
||||||
|
const [changed, setChanged] = React.useState(false);
|
||||||
|
const triggerChange = () => setChanged(true);
|
||||||
|
|
||||||
|
const form = useForm(getLoginFormData());
|
||||||
|
|
||||||
|
const handleChange: FormChange = (event, cb) => {
|
||||||
|
form.change(event, cb);
|
||||||
|
triggerChange();
|
||||||
|
};
|
||||||
|
|
||||||
|
const data: LoginFormData = {
|
||||||
|
...form.data
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (data: LoginFormData) => {
|
||||||
|
const errors = await onSubmit(data);
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async () => handleFormSubmit(data, handleSubmit, setChanged);
|
||||||
|
|
||||||
|
return {
|
||||||
|
change: handleChange,
|
||||||
|
data,
|
||||||
|
hasChanged: changed,
|
||||||
|
submit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginForm: React.FC<LoginFormProps> = ({ children, onSubmit }) => {
|
||||||
|
const props = useLoginForm(onSubmit);
|
||||||
|
|
||||||
|
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||||
|
};
|
||||||
|
|
||||||
|
LoginForm.displayName = "LoginForm";
|
||||||
|
export default LoginForm;
|
58
src/auth/hooks/useAuthProvider.ts
Normal file
58
src/auth/hooks/useAuthProvider.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { IMessageContext } from "@saleor/components/messages";
|
||||||
|
import { User } from "@saleor/fragments/types/User";
|
||||||
|
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||||
|
import ApolloClient from "apollo-client";
|
||||||
|
import { MutableRefObject } from "react";
|
||||||
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
|
import { useExternalAuthProvider } from "./useExternalAuthProvider";
|
||||||
|
import { useSaleorAuthProvider } from "./useSaleorAuthProvider";
|
||||||
|
|
||||||
|
export interface UseAuthProvider {
|
||||||
|
logout: () => void;
|
||||||
|
tokenAuthLoading: boolean;
|
||||||
|
tokenRefresh: () => Promise<boolean>;
|
||||||
|
tokenVerifyLoading: boolean;
|
||||||
|
user?: User;
|
||||||
|
autologinPromise?: MutableRefObject<Promise<any>>;
|
||||||
|
}
|
||||||
|
export interface UseAuthProviderOpts {
|
||||||
|
intl: IntlShape;
|
||||||
|
notify: IMessageContext;
|
||||||
|
apolloClient: ApolloClient<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAuthProvider(opts: UseAuthProviderOpts) {
|
||||||
|
const [authPlugin, setAuthPlugin] = useLocalStorage("authPlugin", undefined);
|
||||||
|
|
||||||
|
const saleorAuth = useSaleorAuthProvider({
|
||||||
|
authPlugin,
|
||||||
|
setAuthPlugin,
|
||||||
|
...opts
|
||||||
|
});
|
||||||
|
|
||||||
|
const externalAuth = useExternalAuthProvider({
|
||||||
|
authPlugin,
|
||||||
|
setAuthPlugin,
|
||||||
|
...opts
|
||||||
|
});
|
||||||
|
|
||||||
|
const loginAuth = {
|
||||||
|
login: saleorAuth.login,
|
||||||
|
loginByExternalPlugin: externalAuth.loginByExternalPlugin,
|
||||||
|
loginByToken: saleorAuth.loginByToken,
|
||||||
|
requestLoginByExternalPlugin: externalAuth.requestLoginByExternalPlugin
|
||||||
|
};
|
||||||
|
|
||||||
|
if (authPlugin) {
|
||||||
|
return {
|
||||||
|
...externalAuth,
|
||||||
|
...loginAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...saleorAuth,
|
||||||
|
...loginAuth
|
||||||
|
};
|
||||||
|
}
|
262
src/auth/hooks/useExternalAuthProvider.ts
Normal file
262
src/auth/hooks/useExternalAuthProvider.ts
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
|
import { User } from "@saleor/fragments/types/User";
|
||||||
|
import { SetLocalStorage } from "@saleor/hooks/useLocalStorage";
|
||||||
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { getMutationStatus } from "@saleor/misc";
|
||||||
|
import errorTracker from "@saleor/services/errorTracking";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
|
||||||
|
import {
|
||||||
|
externalAuthenticationUrlMutation,
|
||||||
|
externalObtainAccessTokensMutation,
|
||||||
|
externalTokenRefreshMutation,
|
||||||
|
externalTokenVerifyMutation
|
||||||
|
} from "../mutations";
|
||||||
|
import {
|
||||||
|
ExternalAuthenticationUrl,
|
||||||
|
ExternalAuthenticationUrlVariables
|
||||||
|
} from "../types/ExternalAuthenticationUrl";
|
||||||
|
import {
|
||||||
|
ExternalObtainAccessTokens,
|
||||||
|
ExternalObtainAccessTokens_externalObtainAccessTokens,
|
||||||
|
ExternalObtainAccessTokensVariables
|
||||||
|
} from "../types/ExternalObtainAccessTokens";
|
||||||
|
import {
|
||||||
|
ExternalRefreshToken,
|
||||||
|
ExternalRefreshTokenVariables
|
||||||
|
} from "../types/ExternalRefreshToken";
|
||||||
|
import {
|
||||||
|
ExternalVerifyToken,
|
||||||
|
ExternalVerifyTokenVariables
|
||||||
|
} from "../types/ExternalVerifyToken";
|
||||||
|
import {
|
||||||
|
displayDemoMessage,
|
||||||
|
getTokens,
|
||||||
|
removeTokens,
|
||||||
|
setAuthToken,
|
||||||
|
setTokens
|
||||||
|
} from "../utils";
|
||||||
|
import { UseAuthProvider, UseAuthProviderOpts } from "./useAuthProvider";
|
||||||
|
|
||||||
|
export interface RequestExternalLoginInput {
|
||||||
|
redirectUri: string;
|
||||||
|
}
|
||||||
|
export interface ExternalLoginInput {
|
||||||
|
code: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseExternalAuthProvider extends UseAuthProvider {
|
||||||
|
requestLoginByExternalPlugin: (
|
||||||
|
pluginId: string,
|
||||||
|
input: RequestExternalLoginInput
|
||||||
|
) => Promise<void>;
|
||||||
|
loginByExternalPlugin: (
|
||||||
|
input: ExternalLoginInput
|
||||||
|
) => Promise<ExternalObtainAccessTokens_externalObtainAccessTokens>;
|
||||||
|
}
|
||||||
|
export interface UseExternalAuthProviderOpts extends UseAuthProviderOpts {
|
||||||
|
setAuthPlugin: SetLocalStorage<any>;
|
||||||
|
authPlugin: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistToken = false;
|
||||||
|
|
||||||
|
export function useExternalAuthProvider({
|
||||||
|
apolloClient,
|
||||||
|
authPlugin,
|
||||||
|
intl,
|
||||||
|
notify,
|
||||||
|
setAuthPlugin
|
||||||
|
}: UseExternalAuthProviderOpts): UseExternalAuthProvider {
|
||||||
|
const [userContext, setUserContext] = useState<undefined | User>(undefined);
|
||||||
|
const autologinPromise = useRef<Promise<any>>();
|
||||||
|
const refreshPromise = useRef<Promise<boolean>>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = getTokens().auth;
|
||||||
|
if (authPlugin && !!token && !userContext) {
|
||||||
|
const input = JSON.stringify({
|
||||||
|
token
|
||||||
|
});
|
||||||
|
autologinPromise.current = tokenVerify({
|
||||||
|
variables: { input, pluginId: authPlugin }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (authPlugin && userContext) {
|
||||||
|
const { id, email, firstName, lastName } = userContext;
|
||||||
|
errorTracker.setUserData({
|
||||||
|
email,
|
||||||
|
id,
|
||||||
|
username: `${firstName} ${lastName}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userContext.isStaff) {
|
||||||
|
logout();
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess),
|
||||||
|
title: intl.formatMessage(commonMessages.insufficientPermissions)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userContext]);
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setUserContext(undefined);
|
||||||
|
setAuthPlugin(undefined);
|
||||||
|
removeTokens();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [externalAuthenticationUrl] = useMutation<
|
||||||
|
ExternalAuthenticationUrl,
|
||||||
|
ExternalAuthenticationUrlVariables
|
||||||
|
>(externalAuthenticationUrlMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
const [obtainAccessTokens, obtainAccessTokensResult] = useMutation<
|
||||||
|
ExternalObtainAccessTokens,
|
||||||
|
ExternalObtainAccessTokensVariables
|
||||||
|
>(externalObtainAccessTokensMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onCompleted: ({ externalObtainAccessTokens }) => {
|
||||||
|
if (externalObtainAccessTokens.errors.length > 0) {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = externalObtainAccessTokens.user;
|
||||||
|
|
||||||
|
setUserContext(user);
|
||||||
|
if (user) {
|
||||||
|
setTokens(
|
||||||
|
externalObtainAccessTokens.token,
|
||||||
|
externalObtainAccessTokens.csrfToken,
|
||||||
|
persistToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
const [tokenRefresh] = useMutation<
|
||||||
|
ExternalRefreshToken,
|
||||||
|
ExternalRefreshTokenVariables
|
||||||
|
>(externalTokenRefreshMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
const [tokenVerify, tokenVerifyResult] = useMutation<
|
||||||
|
ExternalVerifyToken,
|
||||||
|
ExternalVerifyTokenVariables
|
||||||
|
>(externalTokenVerifyMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onCompleted: result => {
|
||||||
|
if (result.externalVerify === null) {
|
||||||
|
logout();
|
||||||
|
} else {
|
||||||
|
const user = result.externalVerify?.user;
|
||||||
|
|
||||||
|
if (!!user) {
|
||||||
|
setUserContext(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
|
||||||
|
const obtainAccessTokensOpts = {
|
||||||
|
...obtainAccessTokensResult,
|
||||||
|
status: getMutationStatus(obtainAccessTokensResult)
|
||||||
|
};
|
||||||
|
const tokenVerifyOpts = {
|
||||||
|
...tokenVerifyResult,
|
||||||
|
status: getMutationStatus(tokenVerifyResult)
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
|
if (DEMO_MODE) {
|
||||||
|
displayDemoMessage(intl, notify);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestLoginByExternalPlugin = async (
|
||||||
|
pluginId: string,
|
||||||
|
pluginInput: RequestExternalLoginInput
|
||||||
|
) => {
|
||||||
|
const input = JSON.stringify(pluginInput);
|
||||||
|
const result = await externalAuthenticationUrl({
|
||||||
|
variables: {
|
||||||
|
input,
|
||||||
|
pluginId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result && !result.data.externalAuthenticationUrl.errors.length) {
|
||||||
|
setAuthPlugin(pluginId);
|
||||||
|
|
||||||
|
const authenticationData = JSON.parse(
|
||||||
|
result.data.externalAuthenticationUrl.authenticationData
|
||||||
|
);
|
||||||
|
|
||||||
|
location.href = authenticationData.authorizationUrl;
|
||||||
|
} else {
|
||||||
|
setAuthPlugin(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginByExternalPlugin = async (loginInput: ExternalLoginInput) => {
|
||||||
|
const input = JSON.stringify(loginInput);
|
||||||
|
const result = await obtainAccessTokens({
|
||||||
|
variables: { input, pluginId: authPlugin }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result && !result.data?.externalObtainAccessTokens?.errors?.length) {
|
||||||
|
if (!!onLogin) {
|
||||||
|
onLogin();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setAuthPlugin(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result?.data?.externalObtainAccessTokens;
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshToken = (): Promise<boolean> => {
|
||||||
|
if (!!refreshPromise.current) {
|
||||||
|
return refreshPromise.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const token = getTokens().refresh;
|
||||||
|
const input = JSON.stringify({
|
||||||
|
refreshToken: token
|
||||||
|
});
|
||||||
|
|
||||||
|
return tokenRefresh({ variables: { input, pluginId: authPlugin } }).then(
|
||||||
|
refreshData => {
|
||||||
|
if (!!refreshData.data.externalRefresh?.token) {
|
||||||
|
setAuthToken(refreshData.data.externalRefresh.token, persistToken);
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
autologinPromise,
|
||||||
|
loginByExternalPlugin,
|
||||||
|
logout,
|
||||||
|
requestLoginByExternalPlugin,
|
||||||
|
tokenAuthLoading: obtainAccessTokensOpts.loading,
|
||||||
|
tokenRefresh: refreshToken,
|
||||||
|
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||||
|
user: userContext
|
||||||
|
};
|
||||||
|
}
|
204
src/auth/hooks/useSaleorAuthProvider.ts
Normal file
204
src/auth/hooks/useSaleorAuthProvider.ts
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
|
import { User } from "@saleor/fragments/types/User";
|
||||||
|
import { SetLocalStorage } from "@saleor/hooks/useLocalStorage";
|
||||||
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { getMutationStatus } from "@saleor/misc";
|
||||||
|
import errorTracker from "@saleor/services/errorTracking";
|
||||||
|
import {
|
||||||
|
isSupported as isCredentialsManagementAPISupported,
|
||||||
|
login as loginWithCredentialsManagementAPI,
|
||||||
|
saveCredentials
|
||||||
|
} from "@saleor/utils/credentialsManagement";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { useMutation } from "react-apollo";
|
||||||
|
|
||||||
|
import {
|
||||||
|
tokenAuthMutation,
|
||||||
|
tokenRefreshMutation,
|
||||||
|
tokenVerifyMutation
|
||||||
|
} from "../mutations";
|
||||||
|
import { RefreshToken, RefreshTokenVariables } from "../types/RefreshToken";
|
||||||
|
import {
|
||||||
|
TokenAuth,
|
||||||
|
TokenAuth_tokenCreate,
|
||||||
|
TokenAuthVariables
|
||||||
|
} from "../types/TokenAuth";
|
||||||
|
import { VerifyToken, VerifyTokenVariables } from "../types/VerifyToken";
|
||||||
|
import {
|
||||||
|
displayDemoMessage,
|
||||||
|
getTokens,
|
||||||
|
removeTokens,
|
||||||
|
setAuthToken,
|
||||||
|
setTokens
|
||||||
|
} from "../utils";
|
||||||
|
import { UseAuthProvider, UseAuthProviderOpts } from "./useAuthProvider";
|
||||||
|
|
||||||
|
export interface UseSaleorAuthProvider extends UseAuthProvider {
|
||||||
|
login: (username: string, password: string) => Promise<TokenAuth_tokenCreate>;
|
||||||
|
loginByToken: (auth: string, csrf: string, user: User) => void;
|
||||||
|
}
|
||||||
|
export interface UseSaleorAuthProviderOpts extends UseAuthProviderOpts {
|
||||||
|
setAuthPlugin: SetLocalStorage<any>;
|
||||||
|
authPlugin: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistToken = false;
|
||||||
|
|
||||||
|
export function useSaleorAuthProvider({
|
||||||
|
apolloClient,
|
||||||
|
authPlugin,
|
||||||
|
intl,
|
||||||
|
notify,
|
||||||
|
setAuthPlugin
|
||||||
|
}: UseSaleorAuthProviderOpts): UseSaleorAuthProvider {
|
||||||
|
const [userContext, setUserContext] = useState<undefined | User>(undefined);
|
||||||
|
const autologinPromise = useRef<Promise<any>>();
|
||||||
|
const refreshPromise = useRef<Promise<boolean>>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = getTokens().auth;
|
||||||
|
if (!authPlugin && !!token && !userContext) {
|
||||||
|
autologinPromise.current = tokenVerify({ variables: { token } });
|
||||||
|
} else if (!authPlugin) {
|
||||||
|
autologinPromise.current = loginWithCredentialsManagementAPI(login);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authPlugin && userContext) {
|
||||||
|
const { id, email, firstName, lastName } = userContext;
|
||||||
|
errorTracker.setUserData({
|
||||||
|
email,
|
||||||
|
id,
|
||||||
|
username: `${firstName} ${lastName}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userContext.isStaff) {
|
||||||
|
logout();
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess),
|
||||||
|
title: intl.formatMessage(commonMessages.insufficientPermissions)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [userContext]);
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setUserContext(undefined);
|
||||||
|
if (isCredentialsManagementAPISupported) {
|
||||||
|
navigator.credentials.preventSilentAccess();
|
||||||
|
}
|
||||||
|
removeTokens();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [tokenAuth, tokenAuthResult] = useMutation<
|
||||||
|
TokenAuth,
|
||||||
|
TokenAuthVariables
|
||||||
|
>(tokenAuthMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onCompleted: ({ tokenCreate }) => {
|
||||||
|
if (tokenCreate.errors.length > 0) {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = tokenCreate.user;
|
||||||
|
|
||||||
|
setUserContext(user);
|
||||||
|
if (user) {
|
||||||
|
setTokens(tokenCreate.token, tokenCreate.csrfToken, persistToken);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
const [tokenRefresh] = useMutation<RefreshToken, RefreshTokenVariables>(
|
||||||
|
tokenRefreshMutation,
|
||||||
|
{
|
||||||
|
client: apolloClient,
|
||||||
|
onError: logout
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [tokenVerify, tokenVerifyResult] = useMutation<
|
||||||
|
VerifyToken,
|
||||||
|
VerifyTokenVariables
|
||||||
|
>(tokenVerifyMutation, {
|
||||||
|
client: apolloClient,
|
||||||
|
onCompleted: result => {
|
||||||
|
if (result.tokenVerify === null) {
|
||||||
|
logout();
|
||||||
|
} else {
|
||||||
|
const user = result.tokenVerify?.user;
|
||||||
|
|
||||||
|
if (!!user) {
|
||||||
|
setUserContext(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: logout
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenAuthOpts = {
|
||||||
|
...tokenAuthResult,
|
||||||
|
status: getMutationStatus(tokenAuthResult)
|
||||||
|
};
|
||||||
|
const tokenVerifyOpts = {
|
||||||
|
...tokenVerifyResult,
|
||||||
|
status: getMutationStatus(tokenVerifyResult)
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
|
if (DEMO_MODE) {
|
||||||
|
displayDemoMessage(intl, notify);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async (email: string, password: string) => {
|
||||||
|
setAuthPlugin(undefined);
|
||||||
|
const result = await tokenAuth({ variables: { email, password } });
|
||||||
|
|
||||||
|
if (result && !result.data.tokenCreate.errors.length) {
|
||||||
|
if (!!onLogin) {
|
||||||
|
onLogin();
|
||||||
|
}
|
||||||
|
saveCredentials(result.data.tokenCreate.user, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data.tokenCreate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginByToken = (auth: string, refresh: string, user: User) => {
|
||||||
|
setAuthPlugin(undefined);
|
||||||
|
setUserContext(user);
|
||||||
|
setTokens(auth, refresh, persistToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshToken = (): Promise<boolean> => {
|
||||||
|
if (!!refreshPromise.current) {
|
||||||
|
return refreshPromise.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const token = getTokens().refresh;
|
||||||
|
|
||||||
|
return tokenRefresh({ variables: { token } }).then(refreshData => {
|
||||||
|
if (!!refreshData.data.tokenRefresh?.token) {
|
||||||
|
setAuthToken(refreshData.data.tokenRefresh.token, persistToken);
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
autologinPromise,
|
||||||
|
login,
|
||||||
|
loginByToken,
|
||||||
|
logout,
|
||||||
|
tokenAuthLoading: tokenAuthOpts.loading,
|
||||||
|
tokenRefresh: refreshToken,
|
||||||
|
tokenVerifyLoading: tokenVerifyOpts.loading,
|
||||||
|
user: userContext
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,32 +1,57 @@
|
||||||
import { User } from "@saleor/fragments/types/User";
|
import { User } from "@saleor/fragments/types/User";
|
||||||
import React from "react";
|
import { parse as parseQs } from "qs";
|
||||||
import { Route, Switch } from "react-router-dom";
|
import React, { MutableRefObject } from "react";
|
||||||
|
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||||
|
|
||||||
import Layout from "./components/Layout";
|
import Layout from "./components/Layout";
|
||||||
import {
|
import {
|
||||||
|
ExternalLoginInput,
|
||||||
|
RequestExternalLoginInput
|
||||||
|
} from "./hooks/useExternalAuthProvider";
|
||||||
|
import { ExternalObtainAccessTokens_externalObtainAccessTokens } from "./types/ExternalObtainAccessTokens";
|
||||||
|
import { TokenAuth_tokenCreate } from "./types/TokenAuth";
|
||||||
|
import {
|
||||||
|
LoginUrlQueryParams,
|
||||||
newPasswordPath,
|
newPasswordPath,
|
||||||
passwordResetPath,
|
passwordResetPath,
|
||||||
passwordResetSuccessPath
|
passwordResetSuccessPath
|
||||||
} from "./urls";
|
} from "./urls";
|
||||||
import LoginView from "./views/Login";
|
import LoginViewComponent from "./views/Login";
|
||||||
import NewPassword from "./views/NewPassword";
|
import NewPassword from "./views/NewPassword";
|
||||||
import ResetPassword from "./views/ResetPassword";
|
import ResetPassword from "./views/ResetPassword";
|
||||||
import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
|
import ResetPasswordSuccess from "./views/ResetPasswordSuccess";
|
||||||
|
|
||||||
|
const LoginView: React.FC<RouteComponentProps<any>> = () => {
|
||||||
|
const qs = parseQs(location.search.substr(1));
|
||||||
|
const params: LoginUrlQueryParams = qs;
|
||||||
|
|
||||||
|
return <LoginViewComponent params={params} />;
|
||||||
|
};
|
||||||
|
|
||||||
interface UserContext {
|
interface UserContext {
|
||||||
login: (username: string, password: string) => void;
|
login: (username: string, password: string) => Promise<TokenAuth_tokenCreate>;
|
||||||
|
loginByExternalPlugin: (
|
||||||
|
input: ExternalLoginInput
|
||||||
|
) => Promise<ExternalObtainAccessTokens_externalObtainAccessTokens>;
|
||||||
loginByToken: (auth: string, csrf: string, user: User) => void;
|
loginByToken: (auth: string, csrf: string, user: User) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
|
requestLoginByExternalPlugin: (
|
||||||
|
pluginId: string,
|
||||||
|
input: RequestExternalLoginInput
|
||||||
|
) => Promise<void>;
|
||||||
tokenAuthLoading: boolean;
|
tokenAuthLoading: boolean;
|
||||||
tokenRefresh: () => Promise<boolean>;
|
tokenRefresh: () => Promise<boolean>;
|
||||||
tokenVerifyLoading: boolean;
|
tokenVerifyLoading: boolean;
|
||||||
user?: User;
|
user?: User;
|
||||||
|
autologinPromise?: MutableRefObject<Promise<any>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserContext = React.createContext<UserContext>({
|
export const UserContext = React.createContext<UserContext>({
|
||||||
login: undefined,
|
login: undefined,
|
||||||
|
loginByExternalPlugin: undefined,
|
||||||
loginByToken: undefined,
|
loginByToken: undefined,
|
||||||
logout: undefined,
|
logout: undefined,
|
||||||
|
requestLoginByExternalPlugin: undefined,
|
||||||
tokenAuthLoading: false,
|
tokenAuthLoading: false,
|
||||||
tokenRefresh: undefined,
|
tokenRefresh: undefined,
|
||||||
tokenVerifyLoading: false
|
tokenVerifyLoading: false
|
||||||
|
|
|
@ -82,3 +82,52 @@ export const SetPasswordMutation = TypedMutation<
|
||||||
SetPassword,
|
SetPassword,
|
||||||
SetPasswordVariables
|
SetPasswordVariables
|
||||||
>(setPassword);
|
>(setPassword);
|
||||||
|
|
||||||
|
export const externalAuthenticationUrlMutation = gql`
|
||||||
|
${accountErrorFragment}
|
||||||
|
mutation ExternalAuthenticationUrl($pluginId: String!, $input: JSONString!) {
|
||||||
|
externalAuthenticationUrl(pluginId: $pluginId, input: $input) {
|
||||||
|
authenticationData
|
||||||
|
errors: accountErrors {
|
||||||
|
...AccountErrorFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const externalObtainAccessTokensMutation = gql`
|
||||||
|
${accountErrorFragment}
|
||||||
|
${fragmentUser}
|
||||||
|
mutation ExternalObtainAccessTokens($pluginId: String!, $input: JSONString!) {
|
||||||
|
externalObtainAccessTokens(pluginId: $pluginId, input: $input) {
|
||||||
|
token
|
||||||
|
csrfToken
|
||||||
|
user {
|
||||||
|
...User
|
||||||
|
}
|
||||||
|
errors: accountErrors {
|
||||||
|
...AccountErrorFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const externalTokenRefreshMutation = gql`
|
||||||
|
mutation ExternalRefreshToken($pluginId: String!, $input: JSONString!) {
|
||||||
|
externalRefresh(pluginId: $pluginId, input: $input) {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const externalTokenVerifyMutation = gql`
|
||||||
|
${fragmentUser}
|
||||||
|
mutation ExternalVerifyToken($pluginId: String!, $input: JSONString!) {
|
||||||
|
externalVerify(pluginId: $pluginId, input: $input) {
|
||||||
|
verifyData
|
||||||
|
user {
|
||||||
|
...User
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
12
src/auth/queries.ts
Normal file
12
src/auth/queries.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
export const availableExternalAuthentications = gql`
|
||||||
|
query AvailableExternalAuthentications {
|
||||||
|
shop {
|
||||||
|
availableExternalAuthentications {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
22
src/auth/types/AvailableExternalAuthentications.ts
Normal file
22
src/auth/types/AvailableExternalAuthentications.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: AvailableExternalAuthentications
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AvailableExternalAuthentications_shop_availableExternalAuthentications {
|
||||||
|
__typename: "ExternalAuthentication";
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvailableExternalAuthentications_shop {
|
||||||
|
__typename: "Shop";
|
||||||
|
availableExternalAuthentications: AvailableExternalAuthentications_shop_availableExternalAuthentications[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AvailableExternalAuthentications {
|
||||||
|
shop: AvailableExternalAuthentications_shop;
|
||||||
|
}
|
30
src/auth/types/ExternalAuthenticationUrl.ts
Normal file
30
src/auth/types/ExternalAuthenticationUrl.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { AccountErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: ExternalAuthenticationUrl
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ExternalAuthenticationUrl_externalAuthenticationUrl_errors {
|
||||||
|
__typename: "AccountError";
|
||||||
|
code: AccountErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalAuthenticationUrl_externalAuthenticationUrl {
|
||||||
|
__typename: "ExternalAuthenticationUrl";
|
||||||
|
authenticationData: any | null;
|
||||||
|
errors: ExternalAuthenticationUrl_externalAuthenticationUrl_errors[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalAuthenticationUrl {
|
||||||
|
externalAuthenticationUrl: ExternalAuthenticationUrl_externalAuthenticationUrl | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalAuthenticationUrlVariables {
|
||||||
|
pluginId: string;
|
||||||
|
input: any;
|
||||||
|
}
|
54
src/auth/types/ExternalObtainAccessTokens.ts
Normal file
54
src/auth/types/ExternalObtainAccessTokens.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { PermissionEnum, AccountErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: ExternalObtainAccessTokens
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokens_externalObtainAccessTokens_user_userPermissions {
|
||||||
|
__typename: "UserPermission";
|
||||||
|
code: PermissionEnum;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokens_externalObtainAccessTokens_user_avatar {
|
||||||
|
__typename: "Image";
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokens_externalObtainAccessTokens_user {
|
||||||
|
__typename: "User";
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
isStaff: boolean;
|
||||||
|
userPermissions: (ExternalObtainAccessTokens_externalObtainAccessTokens_user_userPermissions | null)[] | null;
|
||||||
|
avatar: ExternalObtainAccessTokens_externalObtainAccessTokens_user_avatar | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokens_externalObtainAccessTokens_errors {
|
||||||
|
__typename: "AccountError";
|
||||||
|
code: AccountErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokens_externalObtainAccessTokens {
|
||||||
|
__typename: "ExternalObtainAccessTokens";
|
||||||
|
token: string | null;
|
||||||
|
csrfToken: string | null;
|
||||||
|
user: ExternalObtainAccessTokens_externalObtainAccessTokens_user | null;
|
||||||
|
errors: ExternalObtainAccessTokens_externalObtainAccessTokens_errors[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokens {
|
||||||
|
externalObtainAccessTokens: ExternalObtainAccessTokens_externalObtainAccessTokens | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalObtainAccessTokensVariables {
|
||||||
|
pluginId: string;
|
||||||
|
input: any;
|
||||||
|
}
|
21
src/auth/types/ExternalRefreshToken.ts
Normal file
21
src/auth/types/ExternalRefreshToken.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: ExternalRefreshToken
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ExternalRefreshToken_externalRefresh {
|
||||||
|
__typename: "ExternalRefresh";
|
||||||
|
token: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalRefreshToken {
|
||||||
|
externalRefresh: ExternalRefreshToken_externalRefresh | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalRefreshTokenVariables {
|
||||||
|
pluginId: string;
|
||||||
|
input: any;
|
||||||
|
}
|
46
src/auth/types/ExternalVerifyToken.ts
Normal file
46
src/auth/types/ExternalVerifyToken.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { PermissionEnum } from "./../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: ExternalVerifyToken
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ExternalVerifyToken_externalVerify_user_userPermissions {
|
||||||
|
__typename: "UserPermission";
|
||||||
|
code: PermissionEnum;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalVerifyToken_externalVerify_user_avatar {
|
||||||
|
__typename: "Image";
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalVerifyToken_externalVerify_user {
|
||||||
|
__typename: "User";
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
isStaff: boolean;
|
||||||
|
userPermissions: (ExternalVerifyToken_externalVerify_user_userPermissions | null)[] | null;
|
||||||
|
avatar: ExternalVerifyToken_externalVerify_user_avatar | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalVerifyToken_externalVerify {
|
||||||
|
__typename: "ExternalVerify";
|
||||||
|
verifyData: any | null;
|
||||||
|
user: ExternalVerifyToken_externalVerify_user | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalVerifyToken {
|
||||||
|
externalVerify: ExternalVerifyToken_externalVerify | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExternalVerifyTokenVariables {
|
||||||
|
pluginId: string;
|
||||||
|
input: any;
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ export interface SetPassword_setPassword_user {
|
||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
isStaff: boolean;
|
||||||
userPermissions: (SetPassword_setPassword_user_userPermissions | null)[] | null;
|
userPermissions: (SetPassword_setPassword_user_userPermissions | null)[] | null;
|
||||||
avatar: SetPassword_setPassword_user_avatar | null;
|
avatar: SetPassword_setPassword_user_avatar | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ export interface TokenAuth_tokenCreate_user {
|
||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
isStaff: boolean;
|
||||||
userPermissions: (TokenAuth_tokenCreate_user_userPermissions | null)[] | null;
|
userPermissions: (TokenAuth_tokenCreate_user_userPermissions | null)[] | null;
|
||||||
avatar: TokenAuth_tokenCreate_user_avatar | null;
|
avatar: TokenAuth_tokenCreate_user_avatar | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ export interface VerifyToken_tokenVerify_user {
|
||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
isStaff: boolean;
|
||||||
userPermissions: (VerifyToken_tokenVerify_user_userPermissions | null)[] | null;
|
userPermissions: (VerifyToken_tokenVerify_user_userPermissions | null)[] | null;
|
||||||
avatar: VerifyToken_tokenVerify_user_avatar | null;
|
avatar: VerifyToken_tokenVerify_user_avatar | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,18 @@ export const passwordResetSuccessPath = "/reset-password/success/";
|
||||||
export const passwordResetSuccessUrl = passwordResetSuccessPath;
|
export const passwordResetSuccessUrl = passwordResetSuccessPath;
|
||||||
|
|
||||||
export const newPasswordPath = "/new-password/";
|
export const newPasswordPath = "/new-password/";
|
||||||
|
|
||||||
|
export const loginCallbackPath = "/login/callback/";
|
||||||
|
|
||||||
export interface NewPasswordUrlQueryParams {
|
export interface NewPasswordUrlQueryParams {
|
||||||
email: string;
|
email: string;
|
||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
export const newPasswordUrl = (params?: NewPasswordUrlQueryParams) =>
|
export const newPasswordUrl = (params?: NewPasswordUrlQueryParams) =>
|
||||||
newPasswordPath + "?" + stringifyQs(params);
|
newPasswordPath + "?" + stringifyQs(params);
|
||||||
|
|
||||||
|
export interface LoginOpenidconnectUrlQueryParams {
|
||||||
|
code: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
export type LoginUrlQueryParams = LoginOpenidconnectUrlQueryParams;
|
||||||
|
|
|
@ -1,20 +1,93 @@
|
||||||
|
import { APP_DEFAULT_URI, APP_MOUNT_URI } from "@saleor/config";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useUser from "@saleor/hooks/useUser";
|
import useUser from "@saleor/hooks/useUser";
|
||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import urlJoin from "url-join";
|
||||||
|
import useRouter from "use-react-router";
|
||||||
|
|
||||||
import LoginPage, { FormData } from "../components/LoginPage";
|
import LoginPage from "../components/LoginPage";
|
||||||
import { passwordResetUrl } from "../urls";
|
import { LoginFormData } from "../components/LoginPage/form";
|
||||||
|
import { availableExternalAuthentications } from "../queries";
|
||||||
|
import { AvailableExternalAuthentications } from "../types/AvailableExternalAuthentications";
|
||||||
|
import {
|
||||||
|
loginCallbackPath,
|
||||||
|
LoginUrlQueryParams,
|
||||||
|
passwordResetUrl
|
||||||
|
} from "../urls";
|
||||||
|
|
||||||
const LoginView: React.FC = () => {
|
interface LoginViewProps {
|
||||||
|
params: LoginUrlQueryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginView: React.FC<LoginViewProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const { login, user, tokenAuthLoading } = useUser();
|
const { location } = useRouter();
|
||||||
|
const {
|
||||||
|
login,
|
||||||
|
requestLoginByExternalPlugin,
|
||||||
|
loginByExternalPlugin,
|
||||||
|
tokenAuthLoading
|
||||||
|
} = useUser();
|
||||||
|
const [isError, setIsError] = useState(false);
|
||||||
|
const [isExternalError, setIsExternalError] = useState(false);
|
||||||
|
const {
|
||||||
|
data: externalAuthentications,
|
||||||
|
loading: externalAuthenticationsLoading
|
||||||
|
} = useQuery<AvailableExternalAuthentications>(
|
||||||
|
availableExternalAuthentications
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = (data: FormData) => login(data.email, data.password);
|
const handleSubmit = async (data: LoginFormData) => {
|
||||||
|
const result = await login(data.email, data.password);
|
||||||
|
const errors = result?.errors || [];
|
||||||
|
|
||||||
|
setIsExternalError(false);
|
||||||
|
setIsError(!result || errors?.length > 0);
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRequestExternalAuthentication = (pluginId: string) =>
|
||||||
|
requestLoginByExternalPlugin(pluginId, {
|
||||||
|
redirectUri: urlJoin(
|
||||||
|
window.location.origin,
|
||||||
|
APP_MOUNT_URI === APP_DEFAULT_URI ? "" : APP_MOUNT_URI,
|
||||||
|
loginCallbackPath
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleExternalAuthentication = async (code: string, state: string) => {
|
||||||
|
const result = await loginByExternalPlugin({ code, state });
|
||||||
|
const errors = result?.errors || [];
|
||||||
|
|
||||||
|
setIsError(false);
|
||||||
|
if (!result || errors?.length > 0) {
|
||||||
|
setIsExternalError(true);
|
||||||
|
} else {
|
||||||
|
navigate(APP_DEFAULT_URI);
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { code, state } = params;
|
||||||
|
const isCallbackPath = location.pathname.includes(loginCallbackPath);
|
||||||
|
|
||||||
|
if (code && state && isCallbackPath) {
|
||||||
|
handleExternalAuthentication(code, state);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoginPage
|
<LoginPage
|
||||||
error={user === null}
|
error={isError}
|
||||||
disableLoginButton={tokenAuthLoading}
|
externalError={isExternalError}
|
||||||
|
disabled={tokenAuthLoading}
|
||||||
|
externalAuthentications={
|
||||||
|
externalAuthentications?.shop?.availableExternalAuthentications
|
||||||
|
}
|
||||||
|
loading={externalAuthenticationsLoading || tokenAuthLoading}
|
||||||
|
onExternalAuthentication={handleRequestExternalAuthentication}
|
||||||
onPasswordRecovery={() => navigate(passwordResetUrl)}
|
onPasswordRecovery={() => navigate(passwordResetUrl)}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const formErrors = getFormErrors(["name", "descriptionJson"], errors);
|
const formErrors = getFormErrors(["name", "description"], errors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -59,8 +59,8 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
|
||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
data={data.description}
|
data={data.description}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!formErrors.descriptionJson}
|
error={!!formErrors.description}
|
||||||
helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
|
helperText={getProductErrorMessage(formErrors.description, intl)}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
defaultMessage: "Category Description"
|
defaultMessage: "Category Description"
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -56,7 +56,7 @@ function useCategoryUpdateForm(
|
||||||
slug: category?.slug || ""
|
slug: category?.slug || ""
|
||||||
});
|
});
|
||||||
const [description, changeDescription] = useRichText({
|
const [description, changeDescription] = useRichText({
|
||||||
initial: category?.descriptionJson,
|
initial: category?.description,
|
||||||
triggerChange
|
triggerChange
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ export const category: (
|
||||||
startCursor: "YXJyYXljb25uZWN0aW9uOjA="
|
startCursor: "YXJyYXljb25uZWN0aW9uOjA="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
descriptionJson: JSON.stringify(content),
|
description: JSON.stringify(content),
|
||||||
id: "Q2F0ZWdvcnk6NA==",
|
id: "Q2F0ZWdvcnk6NA==",
|
||||||
metadata: [
|
metadata: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,7 +39,7 @@ export interface CategoryCreate_categoryCreate_category {
|
||||||
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
description: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
parent: CategoryCreate_categoryCreate_category_parent | null;
|
parent: CategoryCreate_categoryCreate_category_parent | null;
|
||||||
|
|
|
@ -165,7 +165,7 @@ export interface CategoryDetails_category {
|
||||||
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
description: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
parent: CategoryDetails_category_parent | null;
|
parent: CategoryDetails_category_parent | null;
|
||||||
|
|
|
@ -39,7 +39,7 @@ export interface CategoryUpdate_categoryUpdate_category {
|
||||||
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
description: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
parent: CategoryUpdate_categoryUpdate_category_parent | null;
|
parent: CategoryUpdate_categoryUpdate_category_parent | null;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const CategoryCreateView: React.FC<CategoryCreateViewProps> = ({
|
||||||
const result = await createCategory({
|
const result = await createCategory({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
description: JSON.stringify(formData.description),
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
|
|
|
@ -188,7 +188,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
id,
|
id,
|
||||||
input: {
|
input: {
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
description: JSON.stringify(formData.description),
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { useStyles } from "../styles";
|
import { useStyles } from "../styles";
|
||||||
|
import { ExtendedFormHelperTextProps } from "./types";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -80,6 +81,11 @@ export const ChannelForm: React.FC<ChannelFormProps> = ({
|
||||||
helperText={getChannelsErrorMessage(formErrors?.slug, intl)}
|
helperText={getChannelsErrorMessage(formErrors?.slug, intl)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
FormHelperTextProps={
|
||||||
|
{
|
||||||
|
"data-testid": "slug-text-input-helper-text"
|
||||||
|
} as ExtendedFormHelperTextProps
|
||||||
|
}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
defaultMessage: "Slug",
|
defaultMessage: "Slug",
|
||||||
description: "channel slug"
|
description: "channel slug"
|
||||||
|
@ -124,8 +130,14 @@ export const ChannelForm: React.FC<ChannelFormProps> = ({
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{!!currencyCodes ? (
|
{!!currencyCodes ? (
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
|
data-test-id="channel-currency-select-input"
|
||||||
allowCustomValues
|
allowCustomValues
|
||||||
error={!!formErrors.currencyCode}
|
error={!!formErrors.currencyCode}
|
||||||
|
FormHelperTextProps={
|
||||||
|
{
|
||||||
|
"data-testid": "currency-text-input-helper-text"
|
||||||
|
} as ExtendedFormHelperTextProps
|
||||||
|
}
|
||||||
helperText={getChannelsErrorMessage(
|
helperText={getChannelsErrorMessage(
|
||||||
formErrors?.currencyCode,
|
formErrors?.currencyCode,
|
||||||
intl
|
intl
|
||||||
|
|
5
src/channels/components/ChannelForm/types.ts
Normal file
5
src/channels/components/ChannelForm/types.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { FormHelperTextProps } from "@material-ui/core/FormHelperText";
|
||||||
|
|
||||||
|
export type ExtendedFormHelperTextProps = FormHelperTextProps & {
|
||||||
|
"data-testid": string;
|
||||||
|
};
|
|
@ -1,10 +1,23 @@
|
||||||
import { channelDetailsFragment } from "@saleor/fragments/channels";
|
import {
|
||||||
|
channelDetailsFragment,
|
||||||
|
channelFragment
|
||||||
|
} from "@saleor/fragments/channels";
|
||||||
import makeQuery from "@saleor/hooks/makeQuery";
|
import makeQuery from "@saleor/hooks/makeQuery";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { BaseChannels } from "./types/BaseChannels";
|
||||||
import { Channel, ChannelVariables } from "./types/Channel";
|
import { Channel, ChannelVariables } from "./types/Channel";
|
||||||
import { Channels } from "./types/Channels";
|
import { Channels } from "./types/Channels";
|
||||||
|
|
||||||
|
export const channelsListBase = gql`
|
||||||
|
${channelFragment}
|
||||||
|
query BaseChannels {
|
||||||
|
channels {
|
||||||
|
...ChannelFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const channelsList = gql`
|
export const channelsList = gql`
|
||||||
${channelDetailsFragment}
|
${channelDetailsFragment}
|
||||||
query Channels {
|
query Channels {
|
||||||
|
@ -23,6 +36,9 @@ export const channelDetails = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const useBaseChannelsList = makeQuery<BaseChannels, {}>(
|
||||||
|
channelsListBase
|
||||||
|
);
|
||||||
export const useChannelsList = makeQuery<Channels, {}>(channelsList);
|
export const useChannelsList = makeQuery<Channels, {}>(channelsList);
|
||||||
export const useChannelDetails = makeQuery<Channel, ChannelVariables>(
|
export const useChannelDetails = makeQuery<Channel, ChannelVariables>(
|
||||||
channelDetails
|
channelDetails
|
||||||
|
|
20
src/channels/types/BaseChannels.ts
Normal file
20
src/channels/types/BaseChannels.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: BaseChannels
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface BaseChannels_channels {
|
||||||
|
__typename: "Channel";
|
||||||
|
id: string;
|
||||||
|
isActive: boolean;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
currencyCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaseChannels {
|
||||||
|
channels: BaseChannels_channels[] | null;
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const formErrors = getFormErrors(["name", "descriptionJson"], errors);
|
const formErrors = getFormErrors(["name", "description"], errors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -57,8 +57,8 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
data={data.description}
|
data={data.description}
|
||||||
error={!!formErrors.descriptionJson}
|
error={!!formErrors.description}
|
||||||
helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
|
helperText={getProductErrorMessage(formErrors.description, intl)}
|
||||||
label={intl.formatMessage(commonMessages.description)}
|
label={intl.formatMessage(commonMessages.description)}
|
||||||
name="description"
|
name="description"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -68,7 +68,7 @@ function useCollectionUpdateForm(
|
||||||
slug: collection?.slug || ""
|
slug: collection?.slug || ""
|
||||||
});
|
});
|
||||||
const [description, changeDescription] = useRichText({
|
const [description, changeDescription] = useRichText({
|
||||||
initial: collection?.descriptionJson,
|
initial: collection?.description,
|
||||||
triggerChange
|
triggerChange
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ export const collection: (
|
||||||
publicationDate: null
|
publicationDate: null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
descriptionJson: JSON.stringify(content),
|
description: JSON.stringify(content),
|
||||||
id: "Q29sbGVjdGlvbjox",
|
id: "Q29sbGVjdGlvbjox",
|
||||||
metadata: [
|
metadata: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -102,7 +102,7 @@ export interface CollectionDetails_collection {
|
||||||
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
description: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
products: CollectionDetails_collection_products | null;
|
products: CollectionDetails_collection_products | null;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export interface CollectionUpdate_collectionUpdate_collection {
|
||||||
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
description: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export interface CreateCollection_collectionCreate_collection {
|
||||||
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
descriptionJson: any;
|
description: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ export const CollectionCreate: React.FC<CollectionCreateProps> = ({
|
||||||
input: {
|
input: {
|
||||||
backgroundImage: formData.backgroundImage.value,
|
backgroundImage: formData.backgroundImage.value,
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
description: JSON.stringify(formData.description),
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
|
|
|
@ -198,7 +198,7 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
const handleUpdate = async (formData: CollectionUpdateData) => {
|
const handleUpdate = async (formData: CollectionUpdateData) => {
|
||||||
const input: CollectionInput = {
|
const input: CollectionInput = {
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
description: JSON.stringify(formData.description),
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
|
|
|
@ -61,7 +61,11 @@ const AppHeader: React.FC<AppHeaderProps> = props => {
|
||||||
{anchor =>
|
{anchor =>
|
||||||
anchor ? (
|
anchor ? (
|
||||||
<Portal container={anchor.current}>
|
<Portal container={anchor.current}>
|
||||||
<div className={classes.root} onClick={onBack}>
|
<div
|
||||||
|
className={classes.root}
|
||||||
|
onClick={onBack}
|
||||||
|
data-test-id="app-header-back-button"
|
||||||
|
>
|
||||||
<ArrowBackIcon className={classes.backArrow} />
|
<ArrowBackIcon className={classes.backArrow} />
|
||||||
{children ? (
|
{children ? (
|
||||||
<Typography className={classes.title}>{children}</Typography>
|
<Typography className={classes.title}>{children}</Typography>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { useAuth } from "@saleor/auth/AuthProvider";
|
import { useAuth } from "@saleor/auth/AuthProvider";
|
||||||
import { useChannelsList } from "@saleor/channels/queries";
|
import { useBaseChannelsList } from "@saleor/channels/queries";
|
||||||
import { ChannelDetailsFragment } from "@saleor/fragments/types/ChannelDetailsFragment";
|
import { ChannelFragment } from "@saleor/fragments/types/ChannelFragment";
|
||||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface UseAppChannel {
|
interface UseAppChannel {
|
||||||
availableChannels: ChannelDetailsFragment[];
|
availableChannels: ChannelFragment[];
|
||||||
channel: ChannelDetailsFragment;
|
channel: ChannelFragment;
|
||||||
isPickerActive: boolean;
|
isPickerActive: boolean;
|
||||||
refreshChannels: () => void;
|
refreshChannels: () => void;
|
||||||
setChannel: (id: string) => void;
|
setChannel: (id: string) => void;
|
||||||
|
@ -27,14 +27,14 @@ const AppChannelContext = React.createContext<AppChannelContextData>({
|
||||||
export const AppChannelProvider: React.FC = ({ children }) => {
|
export const AppChannelProvider: React.FC = ({ children }) => {
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const [selectedChannel, setSelectedChannel] = useLocalStorage("channel", "");
|
const [selectedChannel, setSelectedChannel] = useLocalStorage("channel", "");
|
||||||
const { data: channelData, refetch } = useChannelsList({
|
const { data: channelData, refetch } = useBaseChannelsList({
|
||||||
skip: !isAuthenticated
|
skip: !isAuthenticated
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isPickerActive, setPickerActive] = React.useState(false);
|
const [isPickerActive, setPickerActive] = React.useState(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!selectedChannel) {
|
if (!selectedChannel && channelData?.channels) {
|
||||||
setSelectedChannel(channelData?.channels[0].id);
|
setSelectedChannel(channelData.channels[0].id);
|
||||||
}
|
}
|
||||||
}, [channelData]);
|
}, [channelData]);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||||
import { ChannelDetailsFragment } from "@saleor/fragments/types/ChannelDetailsFragment";
|
import { ChannelFragment } from "@saleor/fragments/types/ChannelFragment";
|
||||||
import { ChannelProps } from "@saleor/types";
|
import { ChannelProps } from "@saleor/types";
|
||||||
import { mapNodeToChoice } from "@saleor/utils/maps";
|
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -22,7 +22,7 @@ const useStyles = makeStyles(
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface AppChannelSelectProps extends ChannelProps {
|
export interface AppChannelSelectProps extends ChannelProps {
|
||||||
channels: ChannelDetailsFragment[];
|
channels: ChannelFragment[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
onChannelSelect: (id: string) => void;
|
onChannelSelect: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,8 +239,11 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||||
</div>
|
</div>
|
||||||
<main className={classes.view}>
|
<main className={classes.view}>
|
||||||
{appState.error
|
{appState.error
|
||||||
? appState.error === "unhandled" && (
|
? appState.error.type === "unhandled" && (
|
||||||
<ErrorPage onBack={handleErrorBack} />
|
<ErrorPage
|
||||||
|
id={appState.error.id}
|
||||||
|
onBack={handleErrorBack}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
: children}
|
: children}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { AttributeReference } from "@saleor/attributes/utils/data";
|
||||||
|
import React from "react";
|
||||||
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import AssignContainerDialog, {
|
||||||
|
AssignContainerDialogProps
|
||||||
|
} from "../AssignContainerDialog";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
header: {
|
||||||
|
defaultMessage: "Assign Attribute Value",
|
||||||
|
description: "dialog header"
|
||||||
|
},
|
||||||
|
searchLabel: {
|
||||||
|
defaultMessage: "Search Attribute Value",
|
||||||
|
description: "label"
|
||||||
|
},
|
||||||
|
searchPlaceholder: {
|
||||||
|
defaultMessage: "Search by value name, etc...",
|
||||||
|
description: "placeholder"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface AssignAttributeValueDialogProps
|
||||||
|
extends Omit<
|
||||||
|
AssignContainerDialogProps,
|
||||||
|
"containers" | "title" | "search" | "confirmButtonState"
|
||||||
|
> {
|
||||||
|
attributeValues: AttributeReference[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssignAttributeValueDialog: React.FC<AssignAttributeValueDialogProps> = ({
|
||||||
|
attributeValues,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AssignContainerDialog
|
||||||
|
containers={attributeValues.map(value => ({
|
||||||
|
id: value.value,
|
||||||
|
name: value.label
|
||||||
|
}))}
|
||||||
|
search={{
|
||||||
|
label: intl.formatMessage(messages.searchLabel),
|
||||||
|
placeholder: intl.formatMessage(messages.searchPlaceholder)
|
||||||
|
}}
|
||||||
|
title={intl.formatMessage(messages.header)}
|
||||||
|
confirmButtonState="default"
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AssignAttributeValueDialog.displayName = "AssignAttributeValueDialog";
|
||||||
|
export default AssignAttributeValueDialog;
|
2
src/components/AssignAttributeValueDialog/index.ts
Normal file
2
src/components/AssignAttributeValueDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./AssignAttributeValueDialog";
|
||||||
|
export * from "./AssignAttributeValueDialog";
|
|
@ -12,7 +12,10 @@ const props: AttributesProps = {
|
||||||
loading: false,
|
loading: false,
|
||||||
onChange: () => undefined,
|
onChange: () => undefined,
|
||||||
onFileChange: () => undefined,
|
onFileChange: () => undefined,
|
||||||
onMultiChange: () => undefined
|
onMultiChange: () => undefined,
|
||||||
|
onReferencesAddClick: () => undefined,
|
||||||
|
onReferencesRemove: () => undefined,
|
||||||
|
onReferencesReorder: () => undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Attributes / Attributes", module)
|
storiesOf("Attributes / Attributes", module)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import IconButton from "@material-ui/core/IconButton";
|
||||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||||
|
import { AttributeReference } from "@saleor/attributes/utils/data";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import Grid from "@saleor/components/Grid";
|
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
import MultiAutocompleteSelectField, {
|
import MultiAutocompleteSelectField, {
|
||||||
MultiAutocompleteChoiceType
|
MultiAutocompleteChoiceType
|
||||||
|
@ -17,7 +17,11 @@ import { AttributeValueFragment } from "@saleor/fragments/types/AttributeValueFr
|
||||||
import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment";
|
import { PageErrorWithAttributesFragment } from "@saleor/fragments/types/PageErrorWithAttributesFragment";
|
||||||
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
|
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
|
||||||
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
|
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
|
||||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
|
import {
|
||||||
|
AttributeEntityTypeEnum,
|
||||||
|
AttributeInputTypeEnum
|
||||||
|
} from "@saleor/types/globalTypes";
|
||||||
import { getProductErrorMessage } from "@saleor/utils/errors";
|
import { getProductErrorMessage } from "@saleor/utils/errors";
|
||||||
import getPageErrorMessage from "@saleor/utils/errors/page";
|
import getPageErrorMessage from "@saleor/utils/errors/page";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -30,14 +34,21 @@ import {
|
||||||
} from "react-intl";
|
} from "react-intl";
|
||||||
|
|
||||||
import FileUploadField, { FileChoiceType } from "../FileUploadField";
|
import FileUploadField, { FileChoiceType } from "../FileUploadField";
|
||||||
|
import SortableChipsField, {
|
||||||
|
SortableChipsFieldValueType
|
||||||
|
} from "../SortableChipsField";
|
||||||
|
import BasicAttributeRow from "./BasicAttributeRow";
|
||||||
|
import ExtendedAttributeRow from "./ExtendedAttributeRow";
|
||||||
import { VariantAttributeScope } from "./types";
|
import { VariantAttributeScope } from "./types";
|
||||||
|
|
||||||
export interface AttributeInputData {
|
export interface AttributeInputData {
|
||||||
inputType: AttributeInputTypeEnum;
|
inputType: AttributeInputTypeEnum;
|
||||||
|
entityType?: AttributeEntityTypeEnum;
|
||||||
variantAttributeScope?: VariantAttributeScope;
|
variantAttributeScope?: VariantAttributeScope;
|
||||||
isRequired: boolean;
|
isRequired: boolean;
|
||||||
values: AttributeValueFragment[];
|
values: AttributeValueFragment[];
|
||||||
selectedValues?: AttributeValueFragment[];
|
selectedValues?: AttributeValueFragment[];
|
||||||
|
references?: AttributeReference[];
|
||||||
}
|
}
|
||||||
export type AttributeInput = FormsetAtomicData<AttributeInputData, string[]>;
|
export type AttributeInput = FormsetAtomicData<AttributeInputData, string[]>;
|
||||||
export type AttributeFileInput = FormsetAtomicData<AttributeInputData, File[]>;
|
export type AttributeFileInput = FormsetAtomicData<AttributeInputData, File[]>;
|
||||||
|
@ -49,9 +60,12 @@ export interface AttributesProps {
|
||||||
ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment
|
ProductErrorWithAttributesFragment | PageErrorWithAttributesFragment
|
||||||
>;
|
>;
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
onChange: FormsetChange;
|
onChange: FormsetChange<string>;
|
||||||
onMultiChange: FormsetChange;
|
onMultiChange: FormsetChange<string>;
|
||||||
onFileChange?: FormsetChange; // TODO: temporairy optional, should be changed to required, after all pages implement it
|
onFileChange: FormsetChange<File>;
|
||||||
|
onReferencesRemove: FormsetChange<string[]>;
|
||||||
|
onReferencesAddClick: (attribute: AttributeInput) => void;
|
||||||
|
onReferencesReorder: FormsetChange<ReorderEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
|
@ -123,6 +137,10 @@ function getMultiChoices(
|
||||||
function getMultiDisplayValue(
|
function getMultiDisplayValue(
|
||||||
attribute: AttributeInput
|
attribute: AttributeInput
|
||||||
): MultiAutocompleteChoiceType[] {
|
): MultiAutocompleteChoiceType[] {
|
||||||
|
if (!attribute.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return attribute.value.map(attributeValue => {
|
return attribute.value.map(attributeValue => {
|
||||||
const definedAttributeValue = attribute.data.values.find(
|
const definedAttributeValue = attribute.data.values.find(
|
||||||
definedValue => definedValue.slug === attributeValue
|
definedValue => definedValue.slug === attributeValue
|
||||||
|
@ -141,6 +159,40 @@ function getMultiDisplayValue(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReferenceDisplayValue(
|
||||||
|
attribute: AttributeInput
|
||||||
|
): SortableChipsFieldValueType[] {
|
||||||
|
if (!attribute.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return attribute.value.map(attributeValue => {
|
||||||
|
const definedAttributeValue = attribute.data.values.find(
|
||||||
|
definedValue => definedValue.reference === attributeValue
|
||||||
|
);
|
||||||
|
// If value has been previously assigned, use it's data
|
||||||
|
if (!!definedAttributeValue) {
|
||||||
|
return {
|
||||||
|
label: definedAttributeValue.name,
|
||||||
|
value: definedAttributeValue.reference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const definedAttributeReference = attribute.data.references?.find(
|
||||||
|
reference => reference.value === attributeValue
|
||||||
|
);
|
||||||
|
// If value has not been yet assigned, use data of reference
|
||||||
|
if (!!definedAttributeReference) {
|
||||||
|
return definedAttributeReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: attributeValue,
|
||||||
|
value: attributeValue
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getSingleChoices(
|
function getSingleChoices(
|
||||||
values: AttributeValueFragment[]
|
values: AttributeValueFragment[]
|
||||||
): SingleAutocompleteChoiceType[] {
|
): SingleAutocompleteChoiceType[] {
|
||||||
|
@ -151,7 +203,7 @@ function getSingleChoices(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileChoice(attribute: AttributeInput): FileChoiceType {
|
function getFileChoice(attribute: AttributeInput): FileChoiceType {
|
||||||
const attributeValue = attribute.value[0];
|
const attributeValue = attribute.value?.length > 0 && attribute.value[0];
|
||||||
|
|
||||||
const definedAttributeValue = attribute.data.values.find(
|
const definedAttributeValue = attribute.data.values.find(
|
||||||
definedValue => definedValue.slug === attributeValue
|
definedValue => definedValue.slug === attributeValue
|
||||||
|
@ -210,7 +262,10 @@ const Attributes: React.FC<AttributesProps> = ({
|
||||||
title,
|
title,
|
||||||
onChange,
|
onChange,
|
||||||
onMultiChange,
|
onMultiChange,
|
||||||
onFileChange
|
onFileChange,
|
||||||
|
onReferencesRemove,
|
||||||
|
onReferencesAddClick,
|
||||||
|
onReferencesReorder
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles({});
|
const classes = useStyles({});
|
||||||
|
@ -255,24 +310,42 @@ const Attributes: React.FC<AttributesProps> = ({
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={attribute.id}>
|
<React.Fragment key={attribute.id}>
|
||||||
{attributeIndex > 0 && <Hr />}
|
{attributeIndex > 0 && <Hr />}
|
||||||
<Grid className={classes.attributeSection} variant="uniform">
|
|
||||||
<div
|
|
||||||
className={classes.attributeSectionLabel}
|
|
||||||
data-test="attribute-label"
|
|
||||||
>
|
|
||||||
<Typography>{attribute.label}</Typography>
|
|
||||||
</div>
|
|
||||||
<div data-test="attribute-value">
|
|
||||||
{attribute.data.inputType ===
|
{attribute.data.inputType ===
|
||||||
|
AttributeInputTypeEnum.REFERENCE ? (
|
||||||
|
<ExtendedAttributeRow
|
||||||
|
label={attribute.label}
|
||||||
|
selectLabel={intl.formatMessage({
|
||||||
|
defaultMessage: "Assign references",
|
||||||
|
description: "button label"
|
||||||
|
})}
|
||||||
|
onSelect={() => onReferencesAddClick(attribute)}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<SortableChipsField
|
||||||
|
values={getReferenceDisplayValue(attribute)}
|
||||||
|
onValueDelete={value =>
|
||||||
|
onReferencesRemove(
|
||||||
|
attribute.id,
|
||||||
|
attribute.value?.filter(id => id !== value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onValueReorder={event =>
|
||||||
|
onReferencesReorder(attribute.id, event)
|
||||||
|
}
|
||||||
|
loading={loading}
|
||||||
|
error={!!error}
|
||||||
|
helperText={getErrorMessage(error, intl)}
|
||||||
|
/>
|
||||||
|
</ExtendedAttributeRow>
|
||||||
|
) : attribute.data.inputType ===
|
||||||
AttributeInputTypeEnum.FILE ? (
|
AttributeInputTypeEnum.FILE ? (
|
||||||
|
<BasicAttributeRow label={attribute.label}>
|
||||||
<FileUploadField
|
<FileUploadField
|
||||||
className={classes.fileField}
|
className={classes.fileField}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
file={getFileChoice(attribute)}
|
file={getFileChoice(attribute)}
|
||||||
onFileUpload={file =>
|
onFileUpload={file => onFileChange(attribute.id, file)}
|
||||||
onFileChange(attribute.id, file)
|
|
||||||
}
|
|
||||||
onFileDelete={() =>
|
onFileDelete={() =>
|
||||||
onFileChange(attribute.id, undefined)
|
onFileChange(attribute.id, undefined)
|
||||||
}
|
}
|
||||||
|
@ -282,8 +355,10 @@ const Attributes: React.FC<AttributesProps> = ({
|
||||||
name: `attribute:${attribute.label}`
|
name: `attribute:${attribute.label}`
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</BasicAttributeRow>
|
||||||
) : attribute.data.inputType ===
|
) : attribute.data.inputType ===
|
||||||
AttributeInputTypeEnum.DROPDOWN ? (
|
AttributeInputTypeEnum.DROPDOWN ? (
|
||||||
|
<BasicAttributeRow label={attribute.label}>
|
||||||
<SingleAutocompleteSelectField
|
<SingleAutocompleteSelectField
|
||||||
choices={getSingleChoices(attribute.data.values)}
|
choices={getSingleChoices(attribute.data.values)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -305,16 +380,16 @@ const Attributes: React.FC<AttributesProps> = ({
|
||||||
}
|
}
|
||||||
allowCustomValues={!attribute.data.isRequired}
|
allowCustomValues={!attribute.data.isRequired}
|
||||||
/>
|
/>
|
||||||
|
</BasicAttributeRow>
|
||||||
) : (
|
) : (
|
||||||
|
<BasicAttributeRow label={attribute.label}>
|
||||||
<MultiAutocompleteSelectField
|
<MultiAutocompleteSelectField
|
||||||
choices={getMultiChoices(attribute.data.values)}
|
choices={getMultiChoices(attribute.data.values)}
|
||||||
displayValues={getMultiDisplayValue(attribute)}
|
displayValues={getMultiDisplayValue(attribute)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={getErrorMessage(error, intl)}
|
helperText={getErrorMessage(error, intl)}
|
||||||
label={intl.formatMessage(
|
label={intl.formatMessage(messages.multipleValueLable)}
|
||||||
messages.multipleValueLable
|
|
||||||
)}
|
|
||||||
name={`attribute:${attribute.label}`}
|
name={`attribute:${attribute.label}`}
|
||||||
value={attribute.value}
|
value={attribute.value}
|
||||||
onChange={event =>
|
onChange={event =>
|
||||||
|
@ -322,9 +397,8 @@ const Attributes: React.FC<AttributesProps> = ({
|
||||||
}
|
}
|
||||||
allowCustomValues={!attribute.data.isRequired}
|
allowCustomValues={!attribute.data.isRequired}
|
||||||
/>
|
/>
|
||||||
|
</BasicAttributeRow>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</Grid>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
44
src/components/Attributes/BasicAttributeRow.tsx
Normal file
44
src/components/Attributes/BasicAttributeRow.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Grid from "@saleor/components/Grid";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
attributeSection: {
|
||||||
|
"&:last-of-type": {
|
||||||
|
paddingBottom: 0
|
||||||
|
},
|
||||||
|
padding: theme.spacing(2, 0)
|
||||||
|
},
|
||||||
|
attributeSectionLabel: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "BasicAttributeRow" }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface BasicAttributeRowProps {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BasicAttributeRow: React.FC<BasicAttributeRowProps> = props => {
|
||||||
|
const { label, children } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid className={classes.attributeSection} variant="uniform">
|
||||||
|
<div
|
||||||
|
className={classes.attributeSectionLabel}
|
||||||
|
data-test="attribute-label"
|
||||||
|
>
|
||||||
|
<Typography>{label}</Typography>
|
||||||
|
</div>
|
||||||
|
<div data-test="attribute-value">{children}</div>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BasicAttributeRow.displayName = "BasicAttributeRow";
|
||||||
|
export default BasicAttributeRow;
|
65
src/components/Attributes/ExtendedAttributeRow.tsx
Normal file
65
src/components/Attributes/ExtendedAttributeRow.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Grid from "@saleor/components/Grid";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
attributeSection: {
|
||||||
|
"&:last-of-type": {
|
||||||
|
paddingBottom: 0
|
||||||
|
},
|
||||||
|
padding: theme.spacing(2, 0)
|
||||||
|
},
|
||||||
|
attributeSectionButton: {
|
||||||
|
float: "right"
|
||||||
|
},
|
||||||
|
attributeSectionLabel: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ExtendedAttributeRow" }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ExtendedAttributeRowProps {
|
||||||
|
label: string;
|
||||||
|
selectLabel: string;
|
||||||
|
disabled: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExtendedAttributeRow: React.FC<ExtendedAttributeRowProps> = props => {
|
||||||
|
const { label, selectLabel, disabled, onSelect, children } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid className={classes.attributeSection} variant="uniform">
|
||||||
|
<div
|
||||||
|
className={classes.attributeSectionLabel}
|
||||||
|
data-test="attribute-label"
|
||||||
|
>
|
||||||
|
<Typography>{label}</Typography>
|
||||||
|
</div>
|
||||||
|
<div data-test="attribute-selector">
|
||||||
|
<Button
|
||||||
|
className={classes.attributeSectionButton}
|
||||||
|
disabled={disabled}
|
||||||
|
variant="text"
|
||||||
|
color="primary"
|
||||||
|
data-test="button-attribute-selector"
|
||||||
|
onClick={onSelect}
|
||||||
|
>
|
||||||
|
{selectLabel}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<div data-test="attribute-value">{children}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtendedAttributeRow.displayName = "ExtendedAttributeRow";
|
||||||
|
export default ExtendedAttributeRow;
|
|
@ -1,4 +1,7 @@
|
||||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
import {
|
||||||
|
AttributeEntityTypeEnum,
|
||||||
|
AttributeInputTypeEnum
|
||||||
|
} from "@saleor/types/globalTypes";
|
||||||
|
|
||||||
import { AttributeInput } from "./Attributes";
|
import { AttributeInput } from "./Attributes";
|
||||||
|
|
||||||
|
@ -12,6 +15,7 @@ const DROPDOWN_ATTRIBUTE: AttributeInput = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "fdinugiffgffd",
|
id: "fdinugiffgffd",
|
||||||
name: "Dropdown First Value",
|
name: "Dropdown First Value",
|
||||||
|
reference: null,
|
||||||
slug: "dropdown-first-value"
|
slug: "dropdown-first-value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -19,6 +23,7 @@ const DROPDOWN_ATTRIBUTE: AttributeInput = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "fdhfdhdihidff",
|
id: "fdhfdhdihidff",
|
||||||
name: "Dropdown Second Value",
|
name: "Dropdown Second Value",
|
||||||
|
reference: null,
|
||||||
slug: "dropdown-second-value"
|
slug: "dropdown-second-value"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -38,6 +43,7 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "terteretregtt",
|
id: "terteretregtt",
|
||||||
name: "Multiselect First Value",
|
name: "Multiselect First Value",
|
||||||
|
reference: null,
|
||||||
slug: "multiselect-first-value"
|
slug: "multiselect-first-value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -45,6 +51,7 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "tyueyryetopwr",
|
id: "tyueyryetopwr",
|
||||||
name: "Multiselect Second Value",
|
name: "Multiselect Second Value",
|
||||||
|
reference: null,
|
||||||
slug: "multiselect-second-value"
|
slug: "multiselect-second-value"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,6 +59,7 @@ const MULTISELECT_ATTRIBUTE: AttributeInput = {
|
||||||
file: null,
|
file: null,
|
||||||
id: "truiwrtweirqd",
|
id: "truiwrtweirqd",
|
||||||
name: "Multiselect Third Value",
|
name: "Multiselect Third Value",
|
||||||
|
reference: null,
|
||||||
slug: "multiselect-third-value"
|
slug: "multiselect-third-value"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -75,19 +83,72 @@ const FILE_ATTRIBUTE: AttributeInput = {
|
||||||
},
|
},
|
||||||
id: "gdghdgdhkkdae",
|
id: "gdghdgdhkkdae",
|
||||||
name: "File First Value",
|
name: "File First Value",
|
||||||
|
reference: null,
|
||||||
slug: "file-first-value"
|
slug: "file-first-value"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
id: "ifudbgidfsb",
|
id: "fguygygugyu",
|
||||||
label: "File Attribute",
|
label: "File Attribute",
|
||||||
value: []
|
value: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const REFERENCE_ATTRIBUTE: AttributeInput = {
|
||||||
|
data: {
|
||||||
|
entityType: AttributeEntityTypeEnum.PAGE,
|
||||||
|
inputType: AttributeInputTypeEnum.REFERENCE,
|
||||||
|
isRequired: true,
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
label: "References First Value",
|
||||||
|
value: "vbnhgcvjhbvhj"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "References Second Value",
|
||||||
|
value: "gucngdfdfvdvd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "References Third Value",
|
||||||
|
value: "dfdfdsfdsfdse"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
__typename: "AttributeValue",
|
||||||
|
file: null,
|
||||||
|
id: "vbnhgcvjhbvhj",
|
||||||
|
name: "References First Value",
|
||||||
|
reference: null,
|
||||||
|
slug: "references-first-value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: "AttributeValue",
|
||||||
|
file: null,
|
||||||
|
id: "gucngdfdfvdvd",
|
||||||
|
name: "References Second Value",
|
||||||
|
reference: null,
|
||||||
|
slug: "references-second-value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: "AttributeValue",
|
||||||
|
file: null,
|
||||||
|
id: "dfdfdsfdsfdse",
|
||||||
|
name: "References Third Value",
|
||||||
|
reference: null,
|
||||||
|
slug: "references-third-value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
id: "kclsmcdsmcs",
|
||||||
|
label: "References Attribute",
|
||||||
|
value: []
|
||||||
|
};
|
||||||
|
|
||||||
export const ATTRIBUTES: AttributeInput[] = [
|
export const ATTRIBUTES: AttributeInput[] = [
|
||||||
DROPDOWN_ATTRIBUTE,
|
DROPDOWN_ATTRIBUTE,
|
||||||
MULTISELECT_ATTRIBUTE,
|
MULTISELECT_ATTRIBUTE,
|
||||||
FILE_ATTRIBUTE
|
FILE_ATTRIBUTE,
|
||||||
|
REFERENCE_ATTRIBUTE
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ATTRIBUTES_SELECTED: AttributeInput[] = [
|
export const ATTRIBUTES_SELECTED: AttributeInput[] = [
|
||||||
|
@ -105,5 +166,13 @@ export const ATTRIBUTES_SELECTED: AttributeInput[] = [
|
||||||
{
|
{
|
||||||
...FILE_ATTRIBUTE,
|
...FILE_ATTRIBUTE,
|
||||||
value: [FILE_ATTRIBUTE.data.values[0].slug]
|
value: [FILE_ATTRIBUTE.data.values[0].slug]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...REFERENCE_ATTRIBUTE,
|
||||||
|
value: [
|
||||||
|
REFERENCE_ATTRIBUTE.data.values[0].id,
|
||||||
|
REFERENCE_ATTRIBUTE.data.values[1].id,
|
||||||
|
REFERENCE_ATTRIBUTE.data.values[2].id
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -17,6 +17,7 @@ const user: User = {
|
||||||
email: "email@example.com",
|
email: "email@example.com",
|
||||||
firstName: "User",
|
firstName: "User",
|
||||||
id: "123",
|
id: "123",
|
||||||
|
isStaff: true,
|
||||||
lastName: "User",
|
lastName: "User",
|
||||||
userPermissions: [
|
userPermissions: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -417,7 +417,11 @@ export const ChannelsAvailability: React.FC<ChannelsAvailabilityProps> = props =
|
||||||
userPermissions={user?.userPermissions || []}
|
userPermissions={user?.userPermissions || []}
|
||||||
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
||||||
>
|
>
|
||||||
<Button color="primary" onClick={openModal}>
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={openModal}
|
||||||
|
data-test-id="channels-availiability-manage-button"
|
||||||
|
>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
defaultMessage: "Manage",
|
defaultMessage: "Manage",
|
||||||
description: "section header button"
|
description: "section header button"
|
||||||
|
|
|
@ -77,7 +77,10 @@ export const ChannelsAvailabilityContent: React.FC<ChannelsAvailabilityContentPr
|
||||||
<Typography className={classes.contentTitle}>
|
<Typography className={classes.contentTitle}>
|
||||||
<FormattedMessage defaultMessage="Channels A to Z" />
|
<FormattedMessage defaultMessage="Channels A to Z" />
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className={classes.scrollArea}>
|
<div
|
||||||
|
className={classes.scrollArea}
|
||||||
|
data-test-id="manage-products-channels-availiability-list"
|
||||||
|
>
|
||||||
{filteredChannels?.length ? (
|
{filteredChannels?.length ? (
|
||||||
filteredChannels.map(option => (
|
filteredChannels.map(option => (
|
||||||
<div key={option.id} className={classes.option}>
|
<div key={option.id} className={classes.option}>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import Chip, { ChipProps } from "@saleor/components/Chip";
|
import Chip, { ChipProps } from "@saleor/components/Chip";
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import CardDecorator from "../../CardDecorator";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
|
|
||||||
const props: ChipProps = {
|
const props: ChipProps = {
|
||||||
label: "Lorem Ipsum"
|
label: "Lorem Ipsum"
|
||||||
};
|
};
|
|
@ -7,6 +7,7 @@ import SVG from "react-inlinesvg";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
export interface ErrorPageProps {
|
export interface ErrorPageProps {
|
||||||
|
id?: string | null;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +32,9 @@ const useStyles = makeStyles(
|
||||||
margin: "0 auto",
|
margin: "0 auto",
|
||||||
width: 830
|
width: 830
|
||||||
},
|
},
|
||||||
|
errorId: {
|
||||||
|
marginTop: theme.spacing(3)
|
||||||
|
},
|
||||||
innerContainer: {
|
innerContainer: {
|
||||||
[theme.breakpoints.down("sm")]: {
|
[theme.breakpoints.down("sm")]: {
|
||||||
order: 1,
|
order: 1,
|
||||||
|
@ -58,7 +62,7 @@ const useStyles = makeStyles(
|
||||||
);
|
);
|
||||||
|
|
||||||
const ErrorPage: React.FC<ErrorPageProps> = props => {
|
const ErrorPage: React.FC<ErrorPageProps> = props => {
|
||||||
const { onBack } = props;
|
const { onBack, id } = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
@ -79,6 +83,11 @@ const ErrorPage: React.FC<ErrorPageProps> = props => {
|
||||||
<Typography>
|
<Typography>
|
||||||
<FormattedMessage defaultMessage="Don't worry, everything is gonna be fine" />
|
<FormattedMessage defaultMessage="Don't worry, everything is gonna be fine" />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{!!id && (
|
||||||
|
<Typography variant="subtitle2" className={classes.errorId}>
|
||||||
|
Error ID: {id}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -3,7 +3,6 @@ import FormControl from "@material-ui/core/FormControl";
|
||||||
import FormHelperText from "@material-ui/core/FormHelperText";
|
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||||
import InputLabel from "@material-ui/core/InputLabel";
|
import InputLabel from "@material-ui/core/InputLabel";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import Undo from "editorjs-undo";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { RichTextEditorContentProps, tools } from "./RichTextEditorContent";
|
import { RichTextEditorContentProps, tools } from "./RichTextEditorContent";
|
||||||
|
@ -34,6 +33,8 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
const [isFocused, setFocus] = React.useState(false);
|
const [isFocused, setFocus] = React.useState(false);
|
||||||
const editor = React.useRef<EditorJS>();
|
const editor = React.useRef<EditorJS>();
|
||||||
const editorContainer = React.useRef<HTMLDivElement>();
|
const editorContainer = React.useRef<HTMLDivElement>();
|
||||||
|
const prevTogglePromise = React.useRef<Promise<boolean>>(); // used to await subsequent toggle invocations
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
() => {
|
() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -46,8 +47,10 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
onChange(savedData);
|
onChange(savedData);
|
||||||
},
|
},
|
||||||
onReady: () => {
|
onReady: () => {
|
||||||
const undo = new Undo({ editor });
|
// FIXME: This throws an error and is not working
|
||||||
undo.initialize(data);
|
// const undo = new Undo({ editor });
|
||||||
|
// undo.initialize(data);
|
||||||
|
|
||||||
if (onReady) {
|
if (onReady) {
|
||||||
onReady();
|
onReady();
|
||||||
}
|
}
|
||||||
|
@ -62,10 +65,20 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
// Rerender editor only if changed from undefined to defined state
|
// Rerender editor only if changed from undefined to defined state
|
||||||
[data === undefined]
|
[data === undefined]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
const toggle = async () => {
|
||||||
if (editor.current?.readOnly) {
|
if (editor.current?.readOnly) {
|
||||||
editor.current.readOnly.toggle(disabled);
|
// readOnly.toggle() by itself does not enqueue the events and will result in a broken output if invocations overlap
|
||||||
|
// Remove this logic when this is fixed in EditorJS
|
||||||
|
if (prevTogglePromise.current instanceof Promise) {
|
||||||
|
await prevTogglePromise.current;
|
||||||
}
|
}
|
||||||
|
prevTogglePromise.current = editor.current.readOnly.toggle(disabled);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle();
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { InputProps } from "@material-ui/core/Input";
|
import { InputProps } from "@material-ui/core/Input";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import { ExtendedFormHelperTextProps } from "@saleor/channels/components/ChannelForm/types";
|
||||||
import { FetchMoreProps } from "@saleor/types";
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import Downshift, { ControllerStateAndHelpers } from "downshift";
|
import Downshift, { ControllerStateAndHelpers } from "downshift";
|
||||||
|
@ -42,6 +43,7 @@ export interface SingleAutocompleteSelectFieldProps
|
||||||
InputProps?: InputProps;
|
InputProps?: InputProps;
|
||||||
fetchChoices?: (value: string) => void;
|
fetchChoices?: (value: string) => void;
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
|
FormHelperTextProps?: ExtendedFormHelperTextProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
const DebounceAutocomplete: React.ComponentType<DebounceProps<
|
||||||
|
@ -69,6 +71,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
fetchChoices,
|
fetchChoices,
|
||||||
onChange,
|
onChange,
|
||||||
onFetchMore,
|
onFetchMore,
|
||||||
|
FormHelperTextProps,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
@ -178,6 +181,7 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
|
||||||
error={error}
|
error={error}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
helperText={helperText}
|
helperText={helperText}
|
||||||
|
FormHelperTextProps={FormHelperTextProps}
|
||||||
label={label}
|
label={label}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
/>
|
/>
|
||||||
|
|
29
src/components/SortableChip/SortableChip.stories.tsx
Normal file
29
src/components/SortableChip/SortableChip.stories.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import SortableChip, {
|
||||||
|
SortableChipProps
|
||||||
|
} from "@saleor/components/SortableChip";
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
import { SortableContainer } from "react-sortable-hoc";
|
||||||
|
|
||||||
|
const Container = SortableContainer(props => props.children);
|
||||||
|
|
||||||
|
const props: SortableChipProps = {
|
||||||
|
index: 0,
|
||||||
|
label: "Lorem Ipsum"
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Sortable chip", module)
|
||||||
|
.addDecorator(CardDecorator)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("default", () => (
|
||||||
|
<Container>
|
||||||
|
<SortableChip {...props} />
|
||||||
|
</Container>
|
||||||
|
))
|
||||||
|
.add("with x", () => (
|
||||||
|
<Container>
|
||||||
|
<SortableChip {...props} onClose={() => undefined} />
|
||||||
|
</Container>
|
||||||
|
));
|
68
src/components/SortableChip/SortableChip.tsx
Normal file
68
src/components/SortableChip/SortableChip.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import CloseIcon from "@material-ui/icons/Close";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
import { SortableElement, SortableElementProps } from "react-sortable-hoc";
|
||||||
|
|
||||||
|
import SortableHandle from "./SortableHandle";
|
||||||
|
|
||||||
|
export interface SortableChipProps extends SortableElementProps {
|
||||||
|
className?: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
closeIcon: {
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: 16,
|
||||||
|
marginLeft: theme.spacing(),
|
||||||
|
verticalAlign: "middle"
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex"
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
|
borderRadius: 18,
|
||||||
|
display: "inline-block",
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
padding: "6px 12px"
|
||||||
|
},
|
||||||
|
sortableHandle: {
|
||||||
|
marginRight: theme.spacing(1)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "SortableChip" }
|
||||||
|
);
|
||||||
|
|
||||||
|
const SortableChip = SortableElement<SortableChipProps>(props => {
|
||||||
|
const { className, label, onClose } = props;
|
||||||
|
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(classes.root, className)}>
|
||||||
|
<div className={classes.content}>
|
||||||
|
<SortableHandle
|
||||||
|
className={classes.sortableHandle}
|
||||||
|
data-test="button-drag-handle"
|
||||||
|
/>
|
||||||
|
<Typography data-test="chip-label">{label}</Typography>
|
||||||
|
{onClose && (
|
||||||
|
<CloseIcon
|
||||||
|
className={classes.closeIcon}
|
||||||
|
onClick={onClose}
|
||||||
|
data-test="button-close"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
SortableChip.displayName = "SortableChip";
|
||||||
|
export default SortableChip;
|
29
src/components/SortableChip/SortableHandle.tsx
Normal file
29
src/components/SortableChip/SortableHandle.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Draggable from "@saleor/icons/Draggable";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
import { SortableHandle as SortableHandleHoc } from "react-sortable-hoc";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
{
|
||||||
|
drag: {
|
||||||
|
cursor: "grab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name: "SortableHandle" }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface SortableHandle {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SortableHandle = SortableHandleHoc(props => {
|
||||||
|
const { className, ...restProps } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Draggable className={classNames(classes.drag, className)} {...restProps} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SortableHandle;
|
2
src/components/SortableChip/index.ts
Normal file
2
src/components/SortableChip/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./SortableChip";
|
||||||
|
export * from "./SortableChip";
|
|
@ -0,0 +1,34 @@
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import SortableChipsField, {
|
||||||
|
SortableChipsFieldProps
|
||||||
|
} from "./SortableChipsField";
|
||||||
|
|
||||||
|
const props: SortableChipsFieldProps = {
|
||||||
|
onValueDelete: () => undefined,
|
||||||
|
onValueReorder: () => undefined,
|
||||||
|
values: [
|
||||||
|
{ label: "Item 1", value: "item-1" },
|
||||||
|
{ label: "Item 2", value: "item-2" },
|
||||||
|
{ label: "Item 3", value: "item-3" },
|
||||||
|
{ label: "Item 4", value: "item-4" },
|
||||||
|
{ label: "Item 5", value: "item-5" },
|
||||||
|
{ label: "Item 6", value: "item-6" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Sortable chips field", module)
|
||||||
|
.addDecorator(CardDecorator)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("default", () => <SortableChipsField {...props} />)
|
||||||
|
.add("loading", () => <SortableChipsField {...props} loading={true} />)
|
||||||
|
.add("with error", () => (
|
||||||
|
<SortableChipsField
|
||||||
|
{...props}
|
||||||
|
error={true}
|
||||||
|
helperText="Something went wrong"
|
||||||
|
/>
|
||||||
|
));
|
84
src/components/SortableChipsField/SortableChipsField.tsx
Normal file
84
src/components/SortableChipsField/SortableChipsField.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { ReorderAction } from "@saleor/types";
|
||||||
|
import React from "react";
|
||||||
|
import { SortableContainerProps } from "react-sortable-hoc";
|
||||||
|
|
||||||
|
import Skeleton from "../Skeleton";
|
||||||
|
import DraggableChip from "../SortableChip";
|
||||||
|
import SortableContainer from "./SortableContainer";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
chip: {
|
||||||
|
background: "#fff",
|
||||||
|
color: theme.palette.primary.dark,
|
||||||
|
marginBottom: theme.spacing(1)
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
color: theme.palette.error.light
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "SortableChipsField"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface SortableChipsFieldValueType {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SortableChipsFieldProps extends SortableContainerProps {
|
||||||
|
loading?: boolean;
|
||||||
|
values: SortableChipsFieldValueType[];
|
||||||
|
error?: boolean;
|
||||||
|
helperText?: string;
|
||||||
|
onValueDelete: (id: string) => void;
|
||||||
|
onValueReorder: ReorderAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SortableChipsField: React.FC<SortableChipsFieldProps> = props => {
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
values,
|
||||||
|
error,
|
||||||
|
helperText,
|
||||||
|
onValueDelete,
|
||||||
|
onValueReorder
|
||||||
|
} = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableContainer
|
||||||
|
axis="xy"
|
||||||
|
lockAxis="xy"
|
||||||
|
useDragHandle
|
||||||
|
onSortEnd={onValueReorder}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{loading ? (
|
||||||
|
<Skeleton />
|
||||||
|
) : (
|
||||||
|
values.map((value, valueIndex) => (
|
||||||
|
<DraggableChip
|
||||||
|
className={classes.chip}
|
||||||
|
key={valueIndex}
|
||||||
|
index={valueIndex}
|
||||||
|
label={value.label}
|
||||||
|
onClose={() => onValueDelete(value.value)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<Typography variant="caption" className={classes.errorText}>
|
||||||
|
{helperText}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</SortableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SortableChipsField.displayName = "SortableChipsField";
|
||||||
|
export default SortableChipsField;
|
5
src/components/SortableChipsField/SortableContainer.tsx
Normal file
5
src/components/SortableChipsField/SortableContainer.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { SortableContainer as SortableContainerHoc } from "react-sortable-hoc";
|
||||||
|
|
||||||
|
const SortableContainer = SortableContainerHoc(({ children }) => children);
|
||||||
|
|
||||||
|
export default SortableContainer;
|
2
src/components/SortableChipsField/index.ts
Normal file
2
src/components/SortableChipsField/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./SortableChipsField";
|
||||||
|
export * from "./SortableChipsField";
|
|
@ -19,11 +19,14 @@ const useStyles = makeStyles(
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
alertDot: {
|
||||||
|
"&:before": { backgroundColor: yellow[500], ...dot }
|
||||||
|
},
|
||||||
errorDot: {
|
errorDot: {
|
||||||
"&:before": { backgroundColor: theme.palette.error.main, ...dot }
|
"&:before": { backgroundColor: theme.palette.error.main, ...dot }
|
||||||
},
|
},
|
||||||
neutralDot: {
|
neutralDot: {
|
||||||
"&:before": { backgroundColor: yellow[500], ...dot }
|
"&:before": { backgroundColor: grey[300], ...dot }
|
||||||
},
|
},
|
||||||
root: {
|
root: {
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
|
@ -35,9 +38,6 @@ const useStyles = makeStyles(
|
||||||
},
|
},
|
||||||
successDot: {
|
successDot: {
|
||||||
"&:before": { backgroundColor: theme.palette.primary.main, ...dot }
|
"&:before": { backgroundColor: theme.palette.primary.main, ...dot }
|
||||||
},
|
|
||||||
unspecifiedDot: {
|
|
||||||
"&:before": { backgroundColor: grey[500], ...dot }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -47,7 +47,7 @@ const useStyles = makeStyles(
|
||||||
interface StatusLabelProps {
|
interface StatusLabelProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
status: "success" | "neutral" | "unspecified" | "error" | string;
|
status: "success" | "alert" | "neutral" | "error" | string;
|
||||||
typographyProps?: TypographyProps;
|
typographyProps?: TypographyProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +62,8 @@ const StatusLabel: React.FC<StatusLabelProps> = props => {
|
||||||
[classes.root]: true,
|
[classes.root]: true,
|
||||||
[className]: true,
|
[className]: true,
|
||||||
[classes.successDot]: status === "success",
|
[classes.successDot]: status === "success",
|
||||||
|
[classes.alertDot]: status === "alert",
|
||||||
[classes.neutralDot]: status === "neutral",
|
[classes.neutralDot]: status === "neutral",
|
||||||
[classes.unspecifiedDot]: status === "unspecified",
|
|
||||||
[classes.errorDot]: status === "error"
|
[classes.errorDot]: status === "error"
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue