diff --git a/CHANGELOG.md b/CHANGELOG.md index 323fc2dba..19c170bb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,3 +42,4 @@ All notable, unreleased changes to this project will be documented in this file. - Fix empty attribute values - #218 by @dominik-zeglen - Add language switch - #213 by @dominik-zeglen - Fix copy - #223, #224 by @dominik-zeglen +- Fix attribute errors - #216 by @dominik-zeglen diff --git a/locale/messages.pot b/locale/messages.pot index 5718fe130..817d55610 100644 --- a/locale/messages.pot +++ b/locale/messages.pot @@ -1,6 +1,6 @@ msgid "" msgstr "" -"POT-Creation-Date: 2019-10-21T14:35:07.777Z\n" +"POT-Creation-Date: 2019-10-21T14:54:01.519Z\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "MIME-Version: 1.0\n" @@ -615,6 +615,14 @@ msgctxt "tab name" msgid "All Webhooks" msgstr "" +#: build/locale/src/products/components/ProductVariantAttributes/ProductVariantAttributes.json +#. [src.products.components.ProductVariantAttributes.1536841622] - product attribute error +#. defaultMessage is: +#. All attributes should have value +msgctxt "product attribute error" +msgid "All attributes should have value" +msgstr "" + #: build/locale/src/taxes/components/TaxConfiguration/TaxConfiguration.json #. [src.taxes.components.TaxConfiguration.142803418] #. defaultMessage is: @@ -8135,6 +8143,14 @@ msgctxt "description" msgid "This unit will be used as default shipping weight" msgstr "" +#: build/locale/src/products/components/ProductVariantAttributes/ProductVariantAttributes.json +#. [src.products.components.ProductVariantAttributes.258966189] - product attribute error +#. defaultMessage is: +#. This variant already exists +msgctxt "product attribute error" +msgid "This variant already exists" +msgstr "" + #: build/locale/src/shipping/components/ShippingZoneRateDialog/ShippingZoneRateDialog.json #. [src.shipping.components.ShippingZoneRateDialog.1486599614] #. defaultMessage is: 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/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 904d7b571..3d0634383 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -90931,7 +90931,7 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = class="MuiFormControl-root-id MuiFormControl-fullWidth-id" >