From db14154c343a5dbd33b7c2d90b70ca5148888b60 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 4 Mar 2020 13:50:25 +0100 Subject: [PATCH] Use error formatting in attribute view --- .../AttributeDetails/AttributeDetails.tsx | 19 ++++-- .../AttributePage/AttributePage.tsx | 5 +- .../AttributeValueEditDialog.tsx | 10 ++- src/attributes/errors.ts | 38 +++++++++++ src/attributes/mutations.ts | 67 ++++++++++--------- src/attributes/types/AttributeBulkDelete.ts | 6 +- src/attributes/types/AttributeCreate.ts | 16 ++--- src/attributes/types/AttributeDelete.ts | 6 +- src/attributes/types/AttributeUpdate.ts | 16 ++--- src/attributes/types/AttributeValueCreate.ts | 16 ++--- src/attributes/types/AttributeValueDelete.ts | 16 ++--- src/attributes/types/AttributeValueReorder.ts | 16 ++--- src/attributes/types/AttributeValueUpdate.ts | 16 ++--- src/attributes/types/ProductErrorFragment.ts | 15 +++++ .../views/AttributeCreate/AttributeCreate.tsx | 51 +++++--------- .../AttributeDetails/AttributeDetails.tsx | 36 +++++----- .../stories/attributes/AttributePage.tsx | 24 ++++++- .../attributes/AttributeValueEditDialog.tsx | 17 ++++- src/types.ts | 2 +- src/utils/errors/common.ts | 12 ++++ src/utils/{errors.ts => errors/index.ts} | 7 +- src/utils/errors/product.ts | 50 ++++++++++++++ 22 files changed, 307 insertions(+), 154 deletions(-) create mode 100644 src/attributes/errors.ts create mode 100644 src/attributes/types/ProductErrorFragment.ts create mode 100644 src/utils/errors/common.ts rename src/utils/{errors.ts => errors/index.ts} (62%) create mode 100644 src/utils/errors/product.ts diff --git a/src/attributes/components/AttributeDetails/AttributeDetails.tsx b/src/attributes/components/AttributeDetails/AttributeDetails.tsx index a0eb97cfe..b4385501d 100644 --- a/src/attributes/components/AttributeDetails/AttributeDetails.tsx +++ b/src/attributes/components/AttributeDetails/AttributeDetails.tsx @@ -10,16 +10,17 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import FormSpacer from "@saleor/components/FormSpacer"; import SingleSelectField from "@saleor/components/SingleSelectField"; import { commonMessages } from "@saleor/intl"; -import { UserError } from "@saleor/types"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; -import { getFieldError } from "@saleor/utils/errors"; +import { getFieldError, getProductErrorMessage } from "@saleor/utils/errors"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; import { AttributePageFormData } from "../AttributePage"; +import { getAttributeSlugErrorMessage } from "../../errors"; export interface AttributeDetailsProps { canChangeType: boolean; data: AttributePageFormData; disabled: boolean; - errors: UserError[]; + errors: ProductErrorFragment[]; onChange: (event: React.ChangeEvent) => void; } @@ -63,7 +64,10 @@ const AttributeDetails: React.FC = ({ })} name={"name" as keyof AttributePageFormData} fullWidth - helperText={getFieldError(errors, "name")?.message} + helperText={getProductErrorMessage( + getFieldError(errors, "name"), + intl + )} value={data.name} onChange={onChange} /> @@ -79,7 +83,7 @@ const AttributeDetails: React.FC = ({ placeholder={slugify(data.name).toLowerCase()} fullWidth helperText={ - getFieldError(errors, "slug")?.message || + getAttributeSlugErrorMessage(getFieldError(errors, "slug"), intl) || intl.formatMessage({ defaultMessage: "This is used internally. Make sure you don’t use spaces", @@ -94,7 +98,10 @@ const AttributeDetails: React.FC = ({ choices={inputTypeChoices} disabled={disabled || !canChangeType} error={!!getFieldError(errors, "inputType")} - hint={getFieldError(errors, "inputType")?.message} + hint={getProductErrorMessage( + getFieldError(errors, "inputType"), + intl + )} label={intl.formatMessage({ defaultMessage: "Catalog Input type for Store Owner", description: "attribute's editor component" diff --git a/src/attributes/components/AttributePage/AttributePage.tsx b/src/attributes/components/AttributePage/AttributePage.tsx index 1cb40118d..69a0f3477 100644 --- a/src/attributes/components/AttributePage/AttributePage.tsx +++ b/src/attributes/components/AttributePage/AttributePage.tsx @@ -12,8 +12,9 @@ import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; import { sectionNames } from "@saleor/intl"; import { maybe } from "@saleor/misc"; -import { ReorderAction, UserError } from "@saleor/types"; +import { ReorderAction } from "@saleor/types"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; import { AttributeDetailsFragment, AttributeDetailsFragment_values @@ -25,7 +26,7 @@ import AttributeValues from "../AttributeValues"; export interface AttributePageProps { attribute: AttributeDetailsFragment | null; disabled: boolean; - errors: UserError[]; + errors: ProductErrorFragment[]; saveButtonBarState: ConfirmButtonTransitionState; values: AttributeDetailsFragment_values[]; onBack: () => void; diff --git a/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx b/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx index 2a4256932..99b526a4b 100644 --- a/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx +++ b/src/attributes/components/AttributeValueEditDialog/AttributeValueEditDialog.tsx @@ -14,8 +14,9 @@ import Form from "@saleor/components/Form"; import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; import { buttonMessages } from "@saleor/intl"; import { maybe } from "@saleor/misc"; -import { UserError } from "@saleor/types"; import { getFieldError } from "@saleor/utils/errors"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { getAttributeValueErrorMessage } from "@saleor/attributes/errors"; import { AttributeDetails_attribute_values } from "../../types/AttributeDetails"; export interface AttributeValueEditDialogFormData { @@ -25,7 +26,7 @@ export interface AttributeValueEditDialogProps { attributeValue: AttributeDetails_attribute_values | null; confirmButtonState: ConfirmButtonTransitionState; disabled: boolean; - errors: UserError[]; + errors: ProductErrorFragment[]; open: boolean; onSubmit: (data: AttributeValueEditDialogFormData) => void; onClose: () => void; @@ -70,7 +71,10 @@ const AttributeValueEditDialog: React.FC = ({ disabled={disabled} error={!!getFieldError(errors, "name")} fullWidth - helperText={getFieldError(errors, "name")?.message} + helperText={getAttributeValueErrorMessage( + getFieldError(errors, "name"), + intl + )} name={"name" as keyof AttributeValueEditDialogFormData} label={intl.formatMessage({ defaultMessage: "Name", diff --git a/src/attributes/errors.ts b/src/attributes/errors.ts new file mode 100644 index 000000000..1b0ad377b --- /dev/null +++ b/src/attributes/errors.ts @@ -0,0 +1,38 @@ +import { IntlShape, defineMessages } from "react-intl"; + +import { ProductErrorCode } from "@saleor/types/globalTypes"; +import { getProductErrorMessage } from "@saleor/utils/errors"; +import { ProductErrorFragment } from "./types/ProductErrorFragment"; + +const messages = defineMessages({ + attributeSlugUnique: { + defaultMessage: "Attribute with this slug already exists" + }, + attributeValueAlreadyExists: { + defaultMessage: "This value already exists within this attribute" + } +}); + +export function getAttributeSlugErrorMessage( + err: ProductErrorFragment, + intl: IntlShape +): string { + switch (err?.code) { + case ProductErrorCode.UNIQUE: + return intl.formatMessage(messages.attributeSlugUnique); + default: + return getProductErrorMessage(err, intl); + } +} + +export function getAttributeValueErrorMessage( + err: ProductErrorFragment, + intl: IntlShape +): string { + switch (err?.code) { + case ProductErrorCode.ALREADY_EXISTS: + return intl.formatMessage(messages.attributeValueAlreadyExists); + default: + return getProductErrorMessage(err, intl); + } +} diff --git a/src/attributes/mutations.ts b/src/attributes/mutations.ts index b70363a7d..5aebd35a2 100644 --- a/src/attributes/mutations.ts +++ b/src/attributes/mutations.ts @@ -35,12 +35,19 @@ import { AttributeValueUpdateVariables } from "./types/AttributeValueUpdate"; +export const productErrorFragment = gql` + fragment ProductErrorFragment on ProductError { + code + field + } +`; + const attributeBulkDelete = gql` + ${productErrorFragment} mutation AttributeBulkDelete($ids: [ID!]!) { attributeBulkDelete(ids: $ids) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } } } @@ -51,11 +58,11 @@ export const AttributeBulkDeleteMutation = TypedMutation< >(attributeBulkDelete); const attributeDelete = gql` + ${productErrorFragment} mutation AttributeDelete($id: ID!) { attributeDelete(id: $id) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } } } @@ -67,15 +74,15 @@ export const AttributeDeleteMutation = TypedMutation< export const attributeUpdateMutation = gql` ${attributeDetailsFragment} + ${productErrorFragment} mutation AttributeUpdate($id: ID!, $input: AttributeUpdateInput!) { attributeUpdate(id: $id, input: $input) { - errors { - field - message - } attribute { ...AttributeDetailsFragment } + errors: productErrors { + ...ProductErrorFragment + } } } `; @@ -86,15 +93,15 @@ export const AttributeUpdateMutation = TypedMutation< const attributeValueDelete = gql` ${attributeDetailsFragment} + ${productErrorFragment} mutation AttributeValueDelete($id: ID!) { attributeValueDelete(id: $id) { - errors { - field - message - } attribute { ...AttributeDetailsFragment } + errors: productErrors { + ...ProductErrorFragment + } } } `; @@ -105,15 +112,15 @@ export const AttributeValueDeleteMutation = TypedMutation< export const attributeValueUpdateMutation = gql` ${attributeDetailsFragment} + ${productErrorFragment} mutation AttributeValueUpdate($id: ID!, $input: AttributeValueCreateInput!) { attributeValueUpdate(id: $id, input: $input) { - errors { - field - message - } attribute { ...AttributeDetailsFragment } + errors: productErrors { + ...ProductErrorFragment + } } } `; @@ -124,15 +131,15 @@ export const AttributeValueUpdateMutation = TypedMutation< export const attributeValueCreateMutation = gql` ${attributeDetailsFragment} + ${productErrorFragment} mutation AttributeValueCreate($id: ID!, $input: AttributeValueCreateInput!) { attributeValueCreate(attribute: $id, input: $input) { - errors { - field - message - } attribute { ...AttributeDetailsFragment } + errors: productErrors { + ...ProductErrorFragment + } } } `; @@ -143,15 +150,15 @@ export const AttributeValueCreateMutation = TypedMutation< export const attributeCreateMutation = gql` ${attributeDetailsFragment} + ${productErrorFragment} mutation AttributeCreate($input: AttributeCreateInput!) { attributeCreate(input: $input) { - errors { - field - message - } attribute { ...AttributeDetailsFragment } + errors: productErrors { + ...ProductErrorFragment + } } } `; @@ -161,18 +168,18 @@ export const AttributeCreateMutation = TypedMutation< >(attributeCreateMutation); const attributeValueReorderMutation = gql` + ${productErrorFragment} mutation AttributeValueReorder($id: ID!, $move: ReorderInput!) { attributeReorderValues(attributeId: $id, moves: [$move]) { - errors { - field - message - } attribute { id values { id } } + errors: productErrors { + ...ProductErrorFragment + } } } `; diff --git a/src/attributes/types/AttributeBulkDelete.ts b/src/attributes/types/AttributeBulkDelete.ts index 6d910e56c..259901950 100644 --- a/src/attributes/types/AttributeBulkDelete.ts +++ b/src/attributes/types/AttributeBulkDelete.ts @@ -2,14 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. +import { ProductErrorCode } from "./../../types/globalTypes"; + // ==================================================== // GraphQL mutation operation: AttributeBulkDelete // ==================================================== export interface AttributeBulkDelete_attributeBulkDelete_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface AttributeBulkDelete_attributeBulkDelete { diff --git a/src/attributes/types/AttributeCreate.ts b/src/attributes/types/AttributeCreate.ts index 5db772935..3c7db619b 100644 --- a/src/attributes/types/AttributeCreate.ts +++ b/src/attributes/types/AttributeCreate.ts @@ -2,18 +2,12 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeCreateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; +import { AttributeCreateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AttributeCreate // ==================================================== -export interface AttributeCreate_attributeCreate_errors { - __typename: "Error"; - field: string | null; - message: string | null; -} - export interface AttributeCreate_attributeCreate_attribute_values { __typename: "AttributeValue"; id: string; @@ -37,10 +31,16 @@ export interface AttributeCreate_attributeCreate_attribute { values: (AttributeCreate_attributeCreate_attribute_values | null)[] | null; } +export interface AttributeCreate_attributeCreate_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + export interface AttributeCreate_attributeCreate { __typename: "AttributeCreate"; - errors: AttributeCreate_attributeCreate_errors[]; attribute: AttributeCreate_attributeCreate_attribute | null; + errors: AttributeCreate_attributeCreate_errors[]; } export interface AttributeCreate { diff --git a/src/attributes/types/AttributeDelete.ts b/src/attributes/types/AttributeDelete.ts index e2cd94ff7..a0273fdac 100644 --- a/src/attributes/types/AttributeDelete.ts +++ b/src/attributes/types/AttributeDelete.ts @@ -2,14 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. +import { ProductErrorCode } from "./../../types/globalTypes"; + // ==================================================== // GraphQL mutation operation: AttributeDelete // ==================================================== export interface AttributeDelete_attributeDelete_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface AttributeDelete_attributeDelete { diff --git a/src/attributes/types/AttributeUpdate.ts b/src/attributes/types/AttributeUpdate.ts index 3529454ca..51dedb082 100644 --- a/src/attributes/types/AttributeUpdate.ts +++ b/src/attributes/types/AttributeUpdate.ts @@ -2,18 +2,12 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeUpdateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; +import { AttributeUpdateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AttributeUpdate // ==================================================== -export interface AttributeUpdate_attributeUpdate_errors { - __typename: "Error"; - field: string | null; - message: string | null; -} - export interface AttributeUpdate_attributeUpdate_attribute_values { __typename: "AttributeValue"; id: string; @@ -37,10 +31,16 @@ export interface AttributeUpdate_attributeUpdate_attribute { values: (AttributeUpdate_attributeUpdate_attribute_values | null)[] | null; } +export interface AttributeUpdate_attributeUpdate_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + export interface AttributeUpdate_attributeUpdate { __typename: "AttributeUpdate"; - errors: AttributeUpdate_attributeUpdate_errors[]; attribute: AttributeUpdate_attributeUpdate_attribute | null; + errors: AttributeUpdate_attributeUpdate_errors[]; } export interface AttributeUpdate { diff --git a/src/attributes/types/AttributeValueCreate.ts b/src/attributes/types/AttributeValueCreate.ts index c11325fb3..9f02505d2 100644 --- a/src/attributes/types/AttributeValueCreate.ts +++ b/src/attributes/types/AttributeValueCreate.ts @@ -2,18 +2,12 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; +import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AttributeValueCreate // ==================================================== -export interface AttributeValueCreate_attributeValueCreate_errors { - __typename: "Error"; - field: string | null; - message: string | null; -} - export interface AttributeValueCreate_attributeValueCreate_attribute_values { __typename: "AttributeValue"; id: string; @@ -37,10 +31,16 @@ export interface AttributeValueCreate_attributeValueCreate_attribute { values: (AttributeValueCreate_attributeValueCreate_attribute_values | null)[] | null; } +export interface AttributeValueCreate_attributeValueCreate_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + export interface AttributeValueCreate_attributeValueCreate { __typename: "AttributeValueCreate"; - errors: AttributeValueCreate_attributeValueCreate_errors[]; attribute: AttributeValueCreate_attributeValueCreate_attribute | null; + errors: AttributeValueCreate_attributeValueCreate_errors[]; } export interface AttributeValueCreate { diff --git a/src/attributes/types/AttributeValueDelete.ts b/src/attributes/types/AttributeValueDelete.ts index 8737ed336..978e938b3 100644 --- a/src/attributes/types/AttributeValueDelete.ts +++ b/src/attributes/types/AttributeValueDelete.ts @@ -2,18 +2,12 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; +import { AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AttributeValueDelete // ==================================================== -export interface AttributeValueDelete_attributeValueDelete_errors { - __typename: "Error"; - field: string | null; - message: string | null; -} - export interface AttributeValueDelete_attributeValueDelete_attribute_values { __typename: "AttributeValue"; id: string; @@ -37,10 +31,16 @@ export interface AttributeValueDelete_attributeValueDelete_attribute { values: (AttributeValueDelete_attributeValueDelete_attribute_values | null)[] | null; } +export interface AttributeValueDelete_attributeValueDelete_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + export interface AttributeValueDelete_attributeValueDelete { __typename: "AttributeValueDelete"; - errors: AttributeValueDelete_attributeValueDelete_errors[]; attribute: AttributeValueDelete_attributeValueDelete_attribute | null; + errors: AttributeValueDelete_attributeValueDelete_errors[]; } export interface AttributeValueDelete { diff --git a/src/attributes/types/AttributeValueReorder.ts b/src/attributes/types/AttributeValueReorder.ts index 31a50ba08..e33438c50 100644 --- a/src/attributes/types/AttributeValueReorder.ts +++ b/src/attributes/types/AttributeValueReorder.ts @@ -2,18 +2,12 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ReorderInput } from "./../../types/globalTypes"; +import { ReorderInput, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AttributeValueReorder // ==================================================== -export interface AttributeValueReorder_attributeReorderValues_errors { - __typename: "Error"; - field: string | null; - message: string | null; -} - export interface AttributeValueReorder_attributeReorderValues_attribute_values { __typename: "AttributeValue"; id: string; @@ -25,10 +19,16 @@ export interface AttributeValueReorder_attributeReorderValues_attribute { values: (AttributeValueReorder_attributeReorderValues_attribute_values | null)[] | null; } +export interface AttributeValueReorder_attributeReorderValues_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + export interface AttributeValueReorder_attributeReorderValues { __typename: "AttributeReorderValues"; - errors: AttributeValueReorder_attributeReorderValues_errors[]; attribute: AttributeValueReorder_attributeReorderValues_attribute | null; + errors: AttributeValueReorder_attributeReorderValues_errors[]; } export interface AttributeValueReorder { diff --git a/src/attributes/types/AttributeValueUpdate.ts b/src/attributes/types/AttributeValueUpdate.ts index 90bd6561d..564f7a691 100644 --- a/src/attributes/types/AttributeValueUpdate.ts +++ b/src/attributes/types/AttributeValueUpdate.ts @@ -2,18 +2,12 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; +import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AttributeValueUpdate // ==================================================== -export interface AttributeValueUpdate_attributeValueUpdate_errors { - __typename: "Error"; - field: string | null; - message: string | null; -} - export interface AttributeValueUpdate_attributeValueUpdate_attribute_values { __typename: "AttributeValue"; id: string; @@ -37,10 +31,16 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute { values: (AttributeValueUpdate_attributeValueUpdate_attribute_values | null)[] | null; } +export interface AttributeValueUpdate_attributeValueUpdate_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + export interface AttributeValueUpdate_attributeValueUpdate { __typename: "AttributeValueUpdate"; - errors: AttributeValueUpdate_attributeValueUpdate_errors[]; attribute: AttributeValueUpdate_attributeValueUpdate_attribute | null; + errors: AttributeValueUpdate_attributeValueUpdate_errors[]; } export interface AttributeValueUpdate { diff --git a/src/attributes/types/ProductErrorFragment.ts b/src/attributes/types/ProductErrorFragment.ts new file mode 100644 index 000000000..2f4d1fccb --- /dev/null +++ b/src/attributes/types/ProductErrorFragment.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ProductErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: ProductErrorFragment +// ==================================================== + +export interface ProductErrorFragment { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} diff --git a/src/attributes/views/AttributeCreate/AttributeCreate.tsx b/src/attributes/views/AttributeCreate/AttributeCreate.tsx index cf700c858..7ece9730e 100644 --- a/src/attributes/views/AttributeCreate/AttributeCreate.tsx +++ b/src/attributes/views/AttributeCreate/AttributeCreate.tsx @@ -5,7 +5,7 @@ import slugify from "slugify"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { maybe } from "@saleor/misc"; -import { ReorderEvent, UserError } from "@saleor/types"; +import { ReorderEvent } from "@saleor/types"; import { add, isSelected, @@ -14,6 +14,8 @@ import { updateAtIndex } from "@saleor/utils/lists"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import AttributePage from "../../components/AttributePage"; import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog"; import AttributeValueEditDialog, { @@ -33,6 +35,12 @@ interface AttributeDetailsProps { params: AttributeAddUrlQueryParams; } +const attributeValueAlreadyExistsError: ProductErrorFragment = { + __typename: "ProductError", + code: ProductErrorCode.ALREADY_EXISTS, + field: "name" +}; + function areValuesEqual( a: AttributeValueEditDialogFormData, b: AttributeValueEditDialogFormData @@ -48,7 +56,9 @@ const AttributeDetails: React.FC = ({ params }) => { const [values, setValues] = React.useState< AttributeValueEditDialogFormData[] >([]); - const [valueErrors, setValueErrors] = React.useState([]); + const [valueErrors, setValueErrors] = React.useState( + [] + ); const id = params.id ? parseInt(params.id, 0) : undefined; @@ -57,6 +67,8 @@ const AttributeDetails: React.FC = ({ params }) => { AttributeAddUrlQueryParams >(navigate, attributeAddUrl, params); + React.useEffect(() => setValueErrors([]), [params.action]); + const handleValueDelete = () => { setValues(remove(values[params.id], values, areValuesEqual)); closeModal(); @@ -73,20 +85,7 @@ const AttributeDetails: React.FC = ({ params }) => { }; const handleValueUpdate = (input: AttributeValueEditDialogFormData) => { if (isSelected(input, values, areValuesEqual)) { - setValueErrors([ - { - field: "name", - message: intl.formatMessage( - { - defaultMessage: "A value named {name} already exists", - description: "attribute value edit error" - }, - { - name: input.name - } - ) - } - ]); + setValueErrors([attributeValueAlreadyExistsError]); } else { setValues(updateAtIndex(input, values, id)); closeModal(); @@ -94,20 +93,7 @@ const AttributeDetails: React.FC = ({ params }) => { }; const handleValueCreate = (input: AttributeValueEditDialogFormData) => { if (isSelected(input, values, areValuesEqual)) { - setValueErrors([ - { - field: "name", - message: intl.formatMessage( - { - defaultMessage: "A value named {name} already exists", - description: "attribute value edit error" - }, - { - name: input.name - } - ) - } - ]); + setValueErrors([attributeValueAlreadyExistsError]); } else { setValues(add(input, values)); closeModal(); @@ -123,10 +109,7 @@ const AttributeDetails: React.FC = ({ params }) => { attributeCreateOpts.data.attributeCreate.errors, - [] - )} + errors={attributeCreateOpts.data?.attributeCreate.errors || []} onBack={() => navigate(attributeListUrl())} onDelete={undefined} onSubmit={input => diff --git a/src/attributes/views/AttributeDetails/AttributeDetails.tsx b/src/attributes/views/AttributeDetails/AttributeDetails.tsx index 3dcc9c8a4..2c00bb10d 100644 --- a/src/attributes/views/AttributeDetails/AttributeDetails.tsx +++ b/src/attributes/views/AttributeDetails/AttributeDetails.tsx @@ -8,6 +8,7 @@ import { maybe } from "@saleor/misc"; import { ReorderEvent } from "@saleor/types"; import { move } from "@saleor/utils/lists"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { getProductErrorMessage } from "@saleor/utils/errors"; import AttributeDeleteDialog from "../../components/AttributeDeleteDialog"; import AttributePage from "../../components/AttributePage"; import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog"; @@ -95,7 +96,10 @@ const AttributeDetails: React.FC = ({ id, params }) => { const handleValueReorderMutation = (data: AttributeValueReorder) => { if (data.attributeReorderValues.errors.length !== 0) { notify({ - text: data.attributeReorderValues.errors[0].message + text: getProductErrorMessage( + data.attributeReorderValues.errors[0], + intl + ) }); } }; @@ -155,12 +159,10 @@ const AttributeDetails: React.FC = ({ id, params }) => { data.attribute)} disabled={loading} - errors={maybe( - () => - attributeUpdateOpts.data - .attributeUpdate.errors, - [] - )} + errors={ + attributeUpdateOpts.data + ?.attributeUpdate.errors || [] + } onBack={() => navigate(attributeListUrl()) } @@ -253,12 +255,10 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeValueCreateOpts.status } disabled={loading} - errors={maybe( - () => - attributeValueCreateOpts.data - .attributeValueCreate.errors, - [] - )} + errors={ + attributeValueCreateOpts.data + ?.attributeValueCreate.errors || [] + } open={params.action === "add-value"} onClose={closeModal} onSubmit={input => @@ -280,12 +280,10 @@ const AttributeDetails: React.FC = ({ id, params }) => { attributeValueUpdateOpts.status } disabled={loading} - errors={maybe( - () => - attributeValueUpdateOpts.data - .attributeValueUpdate.errors, - [] - )} + errors={ + attributeValueUpdateOpts.data + ?.attributeValueUpdate.errors || [] + } open={params.action === "edit-value"} onClose={closeModal} onSubmit={input => diff --git a/src/storybook/stories/attributes/AttributePage.tsx b/src/storybook/stories/attributes/AttributePage.tsx index e2d8f5b68..a7e806031 100644 --- a/src/storybook/stories/attributes/AttributePage.tsx +++ b/src/storybook/stories/attributes/AttributePage.tsx @@ -5,8 +5,10 @@ import AttributePage, { AttributePageProps } from "@saleor/attributes/components/AttributePage"; import { attribute } from "@saleor/attributes/fixtures"; -import { formError } from "@saleor/storybook/misc"; -import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; +import { + AttributeInputTypeEnum, + ProductErrorCode +} from "@saleor/types/globalTypes"; import Decorator from "../../Decorator"; const props: AttributePageProps = { @@ -39,7 +41,23 @@ storiesOf("Views / Attributes / Attribute details", module) .add("form errors", () => ( ({ + __typename: "ProductError", + ...err + }))} /> )) .add("multiple select input", () => ( diff --git a/src/storybook/stories/attributes/AttributeValueEditDialog.tsx b/src/storybook/stories/attributes/AttributeValueEditDialog.tsx index 77d5d6821..b072aece1 100644 --- a/src/storybook/stories/attributes/AttributeValueEditDialog.tsx +++ b/src/storybook/stories/attributes/AttributeValueEditDialog.tsx @@ -2,8 +2,10 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { attribute } from "@saleor/attributes/fixtures"; -import { formError } from "@saleor/storybook/misc"; -import { AttributeValueType } from "@saleor/types/globalTypes"; +import { + AttributeValueType, + ProductErrorCode +} from "@saleor/types/globalTypes"; import AttributeValueEditDialog, { AttributeValueEditDialogProps } from "../../../attributes/components/AttributeValueEditDialog"; @@ -26,5 +28,14 @@ storiesOf("Attributes / Attribute value edit", module) .addDecorator(Decorator) .add("default", () => ) .add("form errors", () => ( - + )); diff --git a/src/types.ts b/src/types.ts index 66d345229..4ba92d2e5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,7 @@ import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelec export interface UserError { field: string | null; - message: string; + message?: string; } export interface DialogProps { diff --git a/src/utils/errors/common.ts b/src/utils/errors/common.ts new file mode 100644 index 000000000..cac94f7c4 --- /dev/null +++ b/src/utils/errors/common.ts @@ -0,0 +1,12 @@ +import { defineMessages } from "react-intl"; + +const commonErrorMessages = defineMessages({ + graphqlError: { + defaultMessage: "API error" + }, + unknownError: { + defaultMessage: "Unknown error" + } +}); + +export default commonErrorMessages; diff --git a/src/utils/errors.ts b/src/utils/errors/index.ts similarity index 62% rename from src/utils/errors.ts rename to src/utils/errors/index.ts index 9f8669d28..e8d61475a 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors/index.ts @@ -1,6 +1,9 @@ import { UserError } from "@saleor/types"; -export function getFieldError(errors: UserError[], field: string): UserError { +export function getFieldError( + errors: T[], + field: string +): T { return errors.find(err => err.field === field); } @@ -9,3 +12,5 @@ export function getErrors(errors: UserError[]): string[] { .filter(err => ["", null].includes(err.field)) .map(err => err.message); } + +export { default as getProductErrorMessage } from "./product"; diff --git a/src/utils/errors/product.ts b/src/utils/errors/product.ts new file mode 100644 index 000000000..a220bb591 --- /dev/null +++ b/src/utils/errors/product.ts @@ -0,0 +1,50 @@ +import { IntlShape, defineMessages } from "react-intl"; + +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; +import { commonMessages } from "@saleor/intl"; +import commonErrorMessages from "./common"; + +const messages = defineMessages({ + attributeAlreadyAssigned: { + defaultMessage: + "This attribute has already been assigned to this product type" + }, + attributeCannotBeAssigned: { + defaultMessage: "This attribute cannot be assigned to this product type" + }, + attributeVariantsDisabled: { + defaultMessage: "Variants are disabled in this product type" + }, + variantNoDigitalContent: { + defaultMessage: "This variant does not have any digital content" + } +}); + +function getProductErrorMessage( + err: ProductErrorFragment, + intl: IntlShape +): string { + if (err) { + switch (err.code) { + case ProductErrorCode.ATTRIBUTE_ALREADY_ASSIGNED: + return intl.formatMessage(messages.attributeAlreadyAssigned); + case ProductErrorCode.ATTRIBUTE_CANNOT_BE_ASSIGNED: + return intl.formatMessage(messages.attributeCannotBeAssigned); + case ProductErrorCode.ATTRIBUTE_VARIANTS_DISABLED: + return intl.formatMessage(messages.attributeVariantsDisabled); + case ProductErrorCode.GRAPHQL_ERROR: + return intl.formatMessage(commonErrorMessages.graphqlError); + case ProductErrorCode.REQUIRED: + return intl.formatMessage(commonMessages.requiredField); + case ProductErrorCode.VARIANT_NO_DIGITAL_CONTENT: + return intl.formatMessage(messages.variantNoDigitalContent); + default: + return intl.formatMessage(commonErrorMessages.unknownError); + } + } + + return undefined; +} + +export default getProductErrorMessage;