diff --git a/schema.graphql b/schema.graphql index 4efcdaaa4..89b27f930 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,6 +1,6 @@ schema { query: Query - mutation: Mutations + mutation: Mutation } type AccountAddressCreate { @@ -405,7 +405,6 @@ type AttributeValueDelete { input AttributeValueInput { id: ID - slug: String values: [String]! } @@ -1990,7 +1989,7 @@ input MoveProductInput { sortOrder: Int } -type Mutations { +type Mutation { webhookCreate(input: WebhookCreateInput!): WebhookCreate webhookDelete(id: ID!): WebhookDelete webhookUpdate(id: ID!, input: WebhookUpdateInput!): WebhookUpdate @@ -3383,6 +3382,8 @@ type Query { serviceAccount(id: ID!): ServiceAccount user(id: ID!): User node(id: ID!): Node + _entities(representations: [_Any]): [_Entity] + _service: _Service } type ReducedRate { @@ -4324,6 +4325,7 @@ enum WebhookEventTypeEnum { ORDER_FULLY_PAID ORDER_UPDATED ORDER_CANCELLED + ORDER_FULFILLED CUSTOMER_CREATED PRODUCT_CREATED } @@ -4361,3 +4363,11 @@ enum WeightUnitsEnum { OZ G } + +scalar _Any + +union _Entity = Address | ServiceAccount | User | ProductVariant | Product | ProductType | Collection | Category | ProductImage + +type _Service { + sdl: String +} diff --git a/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx b/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx index b14ae526e..d80aece07 100644 --- a/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx +++ b/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx @@ -1,9 +1,11 @@ import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; +import Typography from "@material-ui/core/Typography"; import React from "react"; -import { useIntl } from "react-intl"; +import { IntlShape, useIntl } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; +import FormSpacer from "@saleor/components/FormSpacer"; import Grid from "@saleor/components/Grid"; import SingleAutocompleteSelectField, { SingleAutocompleteChoiceType @@ -11,6 +13,8 @@ import SingleAutocompleteSelectField, { import Skeleton from "@saleor/components/Skeleton"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { commonMessages } from "@saleor/intl"; +import { VariantCreate_productVariantCreate_productErrors } from "@saleor/products/types/VariantCreate"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import { ProductVariant_attributes_attribute_values } from "../../types/ProductVariant"; export interface VariantAttributeInputData { @@ -24,7 +28,7 @@ export type VariantAttributeInput = FormsetAtomicData< interface ProductVariantAttributesProps { attributes: VariantAttributeInput[]; disabled: boolean; - errors: Record; + errors: VariantCreate_productVariantCreate_productErrors[]; onChange: FormsetChange; } @@ -63,6 +67,19 @@ function getAttributeValueChoices( })); } +function translateErrors(intl: IntlShape) { + return { + [ProductErrorCode.REQUIRED]: intl.formatMessage({ + defaultMessage: "All attributes should have value", + description: "product attribute error" + }), + [ProductErrorCode.UNIQUE]: intl.formatMessage({ + defaultMessage: "This variant already exists", + description: "product attribute error" + }) + }; +} + const ProductVariantAttributes: React.FC = ({ attributes, disabled, @@ -71,6 +88,8 @@ const ProductVariantAttributes: React.FC = ({ }) => { const intl = useIntl(); + const translatedErrors = translateErrors(intl); + return ( = ({ {attributes === undefined ? ( ) : ( - attributes.map((attribute, attributeIndex) => { - return ( - onChange(attribute.id, event.target.value)} - value={getAttributeValue(attribute.id, attributes)} - choices={getAttributeValueChoices(attribute.id, attributes)} - allowCustomValues - data-tc="variant-attribute-input" - /> - ); - }) + attributes.map(attribute => ( + onChange(attribute.id, event.target.value)} + value={getAttributeValue(attribute.id, attributes)} + choices={getAttributeValueChoices(attribute.id, attributes)} + allowCustomValues + data-tc="variant-attribute-input" + /> + )) )} + {errors.length > 0 && ( + <> + + {errors + .filter(error => error.field === "attributes") + .map(error => ( + + {translatedErrors[error.code]} + + ))} + + )} ); diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index 73c2a4c35..02c677497 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -13,12 +13,9 @@ import useFormset, { FormsetChange, FormsetData } from "@saleor/hooks/useFormset"; -import { - getVariantAttributeErrors, - getVariantAttributeInputFromProduct -} from "@saleor/products/utils/data"; +import { VariantCreate_productVariantCreate_productErrors } from "@saleor/products/types/VariantCreate"; +import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { maybe } from "../../../misc"; -import { UserError } from "../../../types"; import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData"; import ProductVariantAttributes, { VariantAttributeInputData @@ -42,7 +39,7 @@ export interface ProductVariantCreatePageSubmitData interface ProductVariantCreatePageProps { currencySymbol: string; - errors: UserError[]; + errors: VariantCreate_productVariantCreate_productErrors[]; header: string; loading: boolean; product: ProductVariantCreateData_product; @@ -54,7 +51,7 @@ interface ProductVariantCreatePageProps { const ProductVariantCreatePage: React.FC = ({ currencySymbol, - errors: formErrors, + errors: apiErrors, loading, header, product, @@ -96,7 +93,7 @@ const ProductVariantCreatePage: React.FC = ({ }); return ( -
+ {({ change, data, errors, hasChanged, submit, triggerChange }) => { const handleAttributeChange: FormsetChange = (id, value) => { changeAttributeData(id, value); @@ -123,10 +120,7 @@ const ProductVariantCreatePage: React.FC = ({ product.productType.variantAttributes) - )} + errors={apiErrors} onChange={handleAttributeChange} /> diff --git a/src/products/components/ProductVariantPage/ProductVariantPage.tsx b/src/products/components/ProductVariantPage/ProductVariantPage.tsx index 56e3b6499..10ab1036e 100644 --- a/src/products/components/ProductVariantPage/ProductVariantPage.tsx +++ b/src/products/components/ProductVariantPage/ProductVariantPage.tsx @@ -12,12 +12,9 @@ import useFormset, { FormsetChange, FormsetData } from "@saleor/hooks/useFormset"; -import { - getAttributeInputFromVariant, - getVariantAttributeErrors -} from "@saleor/products/utils/data"; +import { VariantUpdate_productVariantUpdate_productErrors } from "@saleor/products/types/VariantUpdate"; +import { getAttributeInputFromVariant } from "@saleor/products/utils/data"; import { maybe } from "../../../misc"; -import { UserError } from "../../../types"; import { ProductVariant } from "../../types/ProductVariant"; import ProductVariantAttributes, { VariantAttributeInputData @@ -42,7 +39,7 @@ export interface ProductVariantPageSubmitData interface ProductVariantPageProps { variant?: ProductVariant; - errors: UserError[]; + errors: VariantUpdate_productVariantUpdate_productErrors[]; saveButtonBarState: ConfirmButtonTransitionState; loading?: boolean; placeholderImage?: string; @@ -56,7 +53,7 @@ interface ProductVariantPageProps { } const ProductVariantPage: React.FC = ({ - errors: formErrors, + errors: apiErrors, loading, header, placeholderImage, @@ -114,7 +111,7 @@ const ProductVariantPage: React.FC = ({ @@ -146,14 +143,7 @@ const ProductVariantPage: React.FC = ({ - variant.attributes.map( - attribute => attribute.attribute - ) - ) - )} + errors={apiErrors} onChange={handleAttributeChange} /> diff --git a/src/products/mutations.ts b/src/products/mutations.ts index c612d576d..45d7fd3c1 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -306,7 +306,8 @@ export const variantUpdateMutation = gql` trackInventory: $trackInventory } ) { - errors { + productErrors { + code field message } @@ -325,7 +326,8 @@ export const variantCreateMutation = gql` ${fragmentVariant} mutation VariantCreate($input: ProductVariantCreateInput!) { productVariantCreate(input: $input) { - errors { + productErrors { + code field message } diff --git a/src/products/types/VariantCreate.ts b/src/products/types/VariantCreate.ts index b1d2783b1..07584e598 100644 --- a/src/products/types/VariantCreate.ts +++ b/src/products/types/VariantCreate.ts @@ -2,14 +2,15 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ProductVariantCreateInput } from "./../../types/globalTypes"; +import { ProductVariantCreateInput, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: VariantCreate // ==================================================== -export interface VariantCreate_productVariantCreate_errors { - __typename: "Error"; +export interface VariantCreate_productVariantCreate_productErrors { + __typename: "ProductError"; + code: ProductErrorCode | null; field: string | null; message: string | null; } @@ -113,7 +114,7 @@ export interface VariantCreate_productVariantCreate_productVariant { export interface VariantCreate_productVariantCreate { __typename: "ProductVariantCreate"; - errors: VariantCreate_productVariantCreate_errors[] | null; + productErrors: VariantCreate_productVariantCreate_productErrors[] | null; productVariant: VariantCreate_productVariantCreate_productVariant | null; } diff --git a/src/products/types/VariantUpdate.ts b/src/products/types/VariantUpdate.ts index f58467f0d..dc7b55e71 100644 --- a/src/products/types/VariantUpdate.ts +++ b/src/products/types/VariantUpdate.ts @@ -2,14 +2,15 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeValueInput } from "./../../types/globalTypes"; +import { AttributeValueInput, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: VariantUpdate // ==================================================== -export interface VariantUpdate_productVariantUpdate_errors { - __typename: "Error"; +export interface VariantUpdate_productVariantUpdate_productErrors { + __typename: "ProductError"; + code: ProductErrorCode | null; field: string | null; message: string | null; } @@ -113,7 +114,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant { export interface VariantUpdate_productVariantUpdate { __typename: "ProductVariantUpdate"; - errors: VariantUpdate_productVariantUpdate_errors[] | null; + productErrors: VariantUpdate_productVariantUpdate_productErrors[] | null; productVariant: VariantUpdate_productVariantUpdate_productVariant | null; } diff --git a/src/products/utils/data.ts b/src/products/utils/data.ts index eb7827abb..735e165bd 100644 --- a/src/products/utils/data.ts +++ b/src/products/utils/data.ts @@ -9,17 +9,10 @@ import { ProductDetails_product_collections, ProductDetails_product_variants } from "@saleor/products/types/ProductDetails"; -import { UserError } from "@saleor/types"; import { ProductAttributeInput } from "../components/ProductAttributes"; import { VariantAttributeInput } from "../components/ProductVariantAttributes"; -import { - ProductVariant, - ProductVariant_attributes_attribute -} from "../types/ProductVariant"; -import { - ProductVariantCreateData_product, - ProductVariantCreateData_product_productType_variantAttributes -} from "../types/ProductVariantCreateData"; +import { ProductVariant } from "../types/ProductVariant"; +import { ProductVariantCreateData_product } from "../types/ProductVariantCreateData"; export interface Collection { id: string; @@ -197,28 +190,3 @@ export function getProductUpdatePageFormData( ) }; } - -export function getVariantAttributeErrors( - errors: UserError[], - variantAttributes: Array< - | ProductVariantCreateData_product_productType_variantAttributes - | ProductVariant_attributes_attribute - > -): Record { - return maybe( - () => - errors.reduce((acc, err) => { - const slug = err.field.split(":")[1]; - const attribute = variantAttributes.find( - attribute => attribute.slug === slug - ); - - if (!!attribute) { - acc[attribute.id] = err.message; - } - - return acc; - }, {}), - {} - ); -} diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index 4d3e1e716..0687b84b7 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -54,7 +54,7 @@ export const ProductVariant: React.StatelessComponent = ({ navigate(productUrl(productId)); }; const handleUpdate = (data: VariantUpdate) => { - if (!maybe(() => data.productVariantUpdate.errors.length)) { + if (!maybe(() => data.productVariantUpdate.productErrors.length)) { notify({ text: intl.formatMessage(commonMessages.savedChanges) }); } }; @@ -74,7 +74,10 @@ export const ProductVariant: React.StatelessComponent = ({ const formTransitionState = getMutationState( updateVariant.opts.called, updateVariant.opts.loading, - maybe(() => updateVariant.opts.data.productVariantUpdate.errors) + maybe( + () => + updateVariant.opts.data.productVariantUpdate.productErrors + ) ); const removeTransitionState = getMutationState( deleteVariant.opts.called, @@ -105,7 +108,9 @@ export const ProductVariant: React.StatelessComponent = ({ data.productVariant.name)} /> updateVariant.opts.data.productVariantUpdate.errors, + () => + updateVariant.opts.data.productVariantUpdate + .productErrors, [] )} saveButtonBarState={formTransitionState} diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index a8ac30785..0db78226c 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -34,7 +34,7 @@ export const ProductVariant: React.StatelessComponent = ({ > {({ data, loading: productLoading }) => { const handleCreateSuccess = (data: VariantCreate) => { - if (data.productVariantCreate.errors.length === 0) { + if (data.productVariantCreate.productErrors.length === 0) { notify({ text: intl.formatMessage({ defaultMessage: "Product created" @@ -83,7 +83,8 @@ export const ProductVariant: React.StatelessComponent = ({ variantCreateResult.called, variantCreateResult.loading, maybe( - () => variantCreateResult.data.productVariantCreate.errors + () => + variantCreateResult.data.productVariantCreate.productErrors ) ); return ( @@ -98,7 +99,8 @@ export const ProductVariant: React.StatelessComponent = ({ currencySymbol={maybe(() => shop.defaultCurrency)} errors={maybe( () => - variantCreateResult.data.productVariantCreate.errors, + variantCreateResult.data.productVariantCreate + .productErrors, [] )} header={intl.formatMessage({ diff --git a/src/storybook/misc.ts b/src/storybook/misc.ts index baf360928..6a28d4117 100644 --- a/src/storybook/misc.ts +++ b/src/storybook/misc.ts @@ -1,4 +1,10 @@ -export const formError = (field: string) => ({ - field, - message: "Generic form error" -}); +export function formError( + field: string, + opts?: Partial> +) { + return { + field, + message: "Generic form error", + ...opts + }; +} diff --git a/src/storybook/stories/products/ProductVariantCreatePage.tsx b/src/storybook/stories/products/ProductVariantCreatePage.tsx index 8ae3a670c..6d42174b3 100644 --- a/src/storybook/stories/products/ProductVariantCreatePage.tsx +++ b/src/storybook/stories/products/ProductVariantCreatePage.tsx @@ -2,7 +2,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import placeholderImage from "@assets/images/placeholder255x255.png"; -import { formError } from "@saleor/storybook/misc"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import ProductVariantCreatePage from "../../../products/components/ProductVariantCreatePage"; import { product as productFixture } from "../../../products/fixtures"; import Decorator from "../../Decorator"; @@ -27,7 +27,24 @@ storiesOf("Views / Products / Create product variant", module) .add("with errors", () => ( ({ + __typename: "ProductError", + message: "Generic form error", + ...error + }))} header="Add variant" loading={false} product={product} diff --git a/src/storybook/stories/products/ProductVariantPage.tsx b/src/storybook/stories/products/ProductVariantPage.tsx index 71bc49bc3..e8413b3e4 100644 --- a/src/storybook/stories/products/ProductVariantPage.tsx +++ b/src/storybook/stories/products/ProductVariantPage.tsx @@ -2,7 +2,7 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import placeholderImage from "@assets/images/placeholder60x60.png"; -import { formError } from "@saleor/storybook/misc"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import ProductVariantPage from "../../../products/components/ProductVariantPage"; import { variant as variantFixture } from "../../../products/fixtures"; import Decorator from "../../Decorator"; @@ -51,6 +51,23 @@ storiesOf("Views / Products / Product variant details", module) onSubmit={() => undefined} onVariantClick={() => undefined} saveButtonBarState="default" - errors={["attributes:Borders", "attributes:Legacy"].map(formError)} + errors={[ + { + code: ProductErrorCode.REQUIRED, + field: "attributes" + }, + { + code: ProductErrorCode.UNIQUE, + field: "attributes" + }, + { + code: ProductErrorCode.ALREADY_EXISTS, + field: "sku" + } + ].map(error => ({ + __typename: "ProductError", + message: "Generic form error", + ...error + }))} /> )); diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 1b4c9abd9..029e1ff9d 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -382,7 +382,6 @@ export interface AttributeValueCreateInput { export interface AttributeValueInput { id?: string | null; - slug?: string | null; values: (string | null)[]; }