diff --git a/src/attributes/components/AttributeDetails/AttributeDetails.tsx b/src/attributes/components/AttributeDetails/AttributeDetails.tsx index a0eb97cfe..26b3ea201 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 { getProductErrorMessage, getFormErrors } 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; } @@ -48,6 +49,8 @@ const AttributeDetails: React.FC = ({ } ]; + const formErrors = getFormErrors(["name", "slug", "inputType"], errors); + return ( = ({ = ({ placeholder={slugify(data.name).toLowerCase()} fullWidth helperText={ - getFieldError(errors, "slug")?.message || + getAttributeSlugErrorMessage(formErrors.slug, intl) || intl.formatMessage({ defaultMessage: "This is used internally. Make sure you don’t use spaces", @@ -93,8 +96,8 @@ const AttributeDetails: React.FC = ({ void; diff --git a/src/attributes/components/AttributeProperties/AttributeProperties.tsx b/src/attributes/components/AttributeProperties/AttributeProperties.tsx index 4e12f1d29..1ad3fcd36 100644 --- a/src/attributes/components/AttributeProperties/AttributeProperties.tsx +++ b/src/attributes/components/AttributeProperties/AttributeProperties.tsx @@ -11,14 +11,14 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import FormSpacer from "@saleor/components/FormSpacer"; import Hr from "@saleor/components/Hr"; import { commonMessages } from "@saleor/intl"; -import { UserError } from "@saleor/types"; -import { getFieldError } from "@saleor/utils/errors"; +import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; import { AttributePageFormData } from "../AttributePage"; export interface AttributePropertiesProps { data: AttributePageFormData; disabled: boolean; - errors: UserError[]; + errors: ProductErrorFragment[]; onChange: (event: React.ChangeEvent) => void; } @@ -30,6 +30,8 @@ const AttributeProperties: React.FC = ({ }) => { const intl = useIntl(); + const formErrors = getFormErrors(["storefrontSearchPosition"], errors); + return ( @@ -86,11 +88,12 @@ const AttributeProperties: React.FC = ({ {data.filterableInStorefront && ( void; onClose: () => void; @@ -45,6 +46,7 @@ const AttributeValueEditDialog: React.FC = ({ name: maybe(() => attributeValue.name, "") }; const errors = useModalDialogErrors(apiErrors, open); + const formErrors = getFormErrors(["name"], errors); return ( @@ -68,9 +70,12 @@ const AttributeValueEditDialog: React.FC = ({ (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/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.tsx b/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.tsx index 96f392455..6398d6d84 100644 --- a/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.tsx +++ b/src/productTypes/components/AssignAttributeDialog/AssignAttributeDialog.tsx @@ -30,6 +30,8 @@ import useSearchQuery from "@saleor/hooks/useSearchQuery"; import { buttonMessages } from "@saleor/intl"; import { maybe, renderCollection } from "@saleor/misc"; import { FetchMoreProps } from "@saleor/types"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { getProductErrorMessage } from "@saleor/utils/errors"; import { SearchAttributes_productType_availableAttributes_edges_node } from "../../hooks/useAvailableAttributeSearch/types/SearchAttributes"; const useStyles = makeStyles( @@ -61,7 +63,7 @@ const useStyles = makeStyles( export interface AssignAttributeDialogProps extends FetchMoreProps { confirmButtonState: ConfirmButtonTransitionState; - errors: string[]; + errors: ProductErrorFragment[]; open: boolean; attributes: SearchAttributes_productType_availableAttributes_edges_node[]; selected: string[]; @@ -188,7 +190,7 @@ const AssignAttributeDialog: React.FC = ({ {errors.map((error, errorIndex) => ( - {error} + {getProductErrorMessage(error, intl)} ))} diff --git a/src/productTypes/components/ProductTypeAttributeEditDialog/ProductTypeAttributeEditDialog.tsx b/src/productTypes/components/ProductTypeAttributeEditDialog/ProductTypeAttributeEditDialog.tsx deleted file mode 100644 index 6a444d2f8..000000000 --- a/src/productTypes/components/ProductTypeAttributeEditDialog/ProductTypeAttributeEditDialog.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import Button from "@material-ui/core/Button"; -import Dialog from "@material-ui/core/Dialog"; -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; -import DialogTitle from "@material-ui/core/DialogTitle"; -import TextField from "@material-ui/core/TextField"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; - -import Form from "@saleor/components/Form"; -import { FormSpacer } from "@saleor/components/FormSpacer"; -import ListField from "@saleor/components/ListField"; -import { buttonMessages } from "@saleor/intl"; -import { UserError } from "@saleor/types"; -import { getFieldError } from "@saleor/utils/errors"; - -export interface FormData { - name: string; - values: Array<{ - label: string; - value: string; - }>; -} - -export interface ProductTypeAttributeEditDialogProps { - disabled: boolean; - errors: UserError[]; - name: string; - opened: boolean; - title: string; - values: Array<{ - label: string; - value: string; - }>; - onClose: () => void; - onConfirm: (data: FormData) => void; -} - -const ProductTypeAttributeEditDialog: React.FC = ({ - disabled, - errors, - name, - opened, - title, - values, - onClose, - onConfirm -}) => { - const intl = useIntl(); - - const initialForm: FormData = { - name: name || "", - values: values || [] - }; - return ( - -
- {({ change, data }) => ( - <> - {title} - - - - - - - - - - - )} -
-
- ); -}; -ProductTypeAttributeEditDialog.displayName = "ProductTypeAttributeEditDialog"; -export default ProductTypeAttributeEditDialog; diff --git a/src/productTypes/components/ProductTypeAttributeEditDialog/index.ts b/src/productTypes/components/ProductTypeAttributeEditDialog/index.ts deleted file mode 100644 index 6344b380f..000000000 --- a/src/productTypes/components/ProductTypeAttributeEditDialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./ProductTypeAttributeEditDialog"; -export * from "./ProductTypeAttributeEditDialog"; diff --git a/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx b/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx index 6616baebd..9d9220f29 100644 --- a/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx +++ b/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx @@ -13,8 +13,8 @@ import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; import useStateFromProps from "@saleor/hooks/useStateFromProps"; import { sectionNames } from "@saleor/intl"; import { ProductTypeDetails_taxTypes } from "@saleor/productTypes/types/ProductTypeDetails"; -import { UserError } from "@saleor/types"; import { WeightUnitsEnum } from "@saleor/types/globalTypes"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; import ProductTypeDetails from "../ProductTypeDetails/ProductTypeDetails"; import ProductTypeShipping from "../ProductTypeShipping/ProductTypeShipping"; import ProductTypeTaxes from "../ProductTypeTaxes/ProductTypeTaxes"; @@ -27,7 +27,7 @@ export interface ProductTypeForm { } export interface ProductTypeCreatePageProps { - errors: UserError[]; + errors: ProductErrorFragment[]; defaultWeightUnit: WeightUnitsEnum; disabled: boolean; pageTitle: string; @@ -106,6 +106,7 @@ const ProductTypeCreatePage: React.FC = ({ disabled={disabled} data={data} defaultWeightUnit={defaultWeightUnit} + errors={errors} onChange={change} /> diff --git a/src/productTypes/components/ProductTypeDetails/ProductTypeDetails.tsx b/src/productTypes/components/ProductTypeDetails/ProductTypeDetails.tsx index 758fd36e6..515e8f2de 100644 --- a/src/productTypes/components/ProductTypeDetails/ProductTypeDetails.tsx +++ b/src/productTypes/components/ProductTypeDetails/ProductTypeDetails.tsx @@ -7,8 +7,8 @@ import { useIntl } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; import { commonMessages } from "@saleor/intl"; -import { UserError } from "@saleor/types"; -import { getFieldError } from "@saleor/utils/errors"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; const useStyles = makeStyles( { @@ -24,16 +24,18 @@ interface ProductTypeDetailsProps { name: string; }; disabled: boolean; - errors: UserError[]; + errors: ProductErrorFragment[]; onChange: (event: React.ChangeEvent) => void; } const ProductTypeDetails: React.FC = props => { const { data, disabled, errors, onChange } = props; - const classes = useStyles(props); + const classes = useStyles(props); const intl = useIntl(); + const formErrors = getFormErrors(["name"], errors); + return ( = props => { = ({ disabled={disabled} data={data} defaultWeightUnit={defaultWeightUnit} + errors={errors} onChange={change} /> diff --git a/src/productTypes/components/ProductTypeShipping/ProductTypeShipping.tsx b/src/productTypes/components/ProductTypeShipping/ProductTypeShipping.tsx index afb146aca..0e5ea818e 100644 --- a/src/productTypes/components/ProductTypeShipping/ProductTypeShipping.tsx +++ b/src/productTypes/components/ProductTypeShipping/ProductTypeShipping.tsx @@ -6,6 +6,8 @@ import { useIntl } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; import { WeightUnitsEnum } from "../../../types/globalTypes"; interface ProductTypeShippingProps { @@ -15,6 +17,7 @@ interface ProductTypeShippingProps { }; defaultWeightUnit: WeightUnitsEnum; disabled: boolean; + errors: ProductErrorFragment[]; onChange: (event: React.ChangeEvent) => void; } @@ -22,10 +25,13 @@ const ProductTypeShipping: React.FC = ({ data, defaultWeightUnit, disabled, + errors, onChange }) => { const intl = useIntl(); + const formErrors = getFormErrors(["weight"], errors); + return ( = ({ {data.isShippingRequired && ( (productTypeDeleteMutation); export const productTypeBulkDeleteMutation = gql` + ${productErrorFragment} mutation ProductTypeBulkDelete($ids: [ID]!) { productTypeBulkDelete(ids: $ids) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } } } @@ -65,12 +66,12 @@ export const TypedProductTypeBulkDeleteMutation = TypedMutation< >(productTypeBulkDeleteMutation); export const productTypeUpdateMutation = gql` + ${productErrorFragment} ${productTypeDetailsFragment} mutation ProductTypeUpdate($id: ID!, $input: ProductTypeInput!) { productTypeUpdate(id: $id, input: $input) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } productType { ...ProductTypeDetailsFragment @@ -84,12 +85,12 @@ export const TypedProductTypeUpdateMutation = TypedMutation< >(productTypeUpdateMutation); export const assignAttributeMutation = gql` + ${productErrorFragment} ${productTypeDetailsFragment} mutation AssignAttribute($id: ID!, $operations: [AttributeAssignInput!]!) { attributeAssign(productTypeId: $id, operations: $operations) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } productType { ...ProductTypeDetailsFragment @@ -103,12 +104,12 @@ export const TypedAssignAttributeMutation = TypedMutation< >(assignAttributeMutation); export const unassignAttributeMutation = gql` + ${productErrorFragment} ${productTypeDetailsFragment} mutation UnassignAttribute($id: ID!, $ids: [ID]!) { attributeUnassign(productTypeId: $id, attributeIds: $ids) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } productType { ...ProductTypeDetailsFragment @@ -122,12 +123,12 @@ export const TypedUnassignAttributeMutation = TypedMutation< >(unassignAttributeMutation); export const productTypeCreateMutation = gql` + ${productErrorFragment} ${productTypeDetailsFragment} mutation ProductTypeCreate($input: ProductTypeInput!) { productTypeCreate(input: $input) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } productType { ...ProductTypeDetailsFragment @@ -141,6 +142,7 @@ export const TypedProductTypeCreateMutation = TypedMutation< >(productTypeCreateMutation); const productTypeAttributeReorder = gql` + ${productErrorFragment} ${productTypeDetailsFragment} mutation ProductTypeAttributeReorder( $move: ReorderInput! @@ -152,9 +154,8 @@ const productTypeAttributeReorder = gql` productTypeId: $productTypeId type: $type ) { - errors { - field - message + errors: productErrors { + ...ProductErrorFragment } productType { ...ProductTypeDetailsFragment diff --git a/src/productTypes/types/AssignAttribute.ts b/src/productTypes/types/AssignAttribute.ts index 2e9d3fb4a..2f24d922a 100644 --- a/src/productTypes/types/AssignAttribute.ts +++ b/src/productTypes/types/AssignAttribute.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AttributeAssignInput } from "./../../types/globalTypes"; +import { AttributeAssignInput, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AssignAttribute // ==================================================== export interface AssignAttribute_attributeAssign_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface AssignAttribute_attributeAssign_productType_taxType { diff --git a/src/productTypes/types/ProductTypeAttributeReorder.ts b/src/productTypes/types/ProductTypeAttributeReorder.ts index 6d4ca443d..7d765230d 100644 --- a/src/productTypes/types/ProductTypeAttributeReorder.ts +++ b/src/productTypes/types/ProductTypeAttributeReorder.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ReorderInput, AttributeTypeEnum } from "./../../types/globalTypes"; +import { ReorderInput, AttributeTypeEnum, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: ProductTypeAttributeReorder // ==================================================== export interface ProductTypeAttributeReorder_productTypeReorderAttributes_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_taxType { diff --git a/src/productTypes/types/ProductTypeBulkDelete.ts b/src/productTypes/types/ProductTypeBulkDelete.ts index 034d3e909..7727f11a3 100644 --- a/src/productTypes/types/ProductTypeBulkDelete.ts +++ b/src/productTypes/types/ProductTypeBulkDelete.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: ProductTypeBulkDelete // ==================================================== export interface ProductTypeBulkDelete_productTypeBulkDelete_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface ProductTypeBulkDelete_productTypeBulkDelete { diff --git a/src/productTypes/types/ProductTypeCreate.ts b/src/productTypes/types/ProductTypeCreate.ts index 5993bbb02..d95f672f1 100644 --- a/src/productTypes/types/ProductTypeCreate.ts +++ b/src/productTypes/types/ProductTypeCreate.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ProductTypeInput } from "./../../types/globalTypes"; +import { ProductTypeInput, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: ProductTypeCreate // ==================================================== export interface ProductTypeCreate_productTypeCreate_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface ProductTypeCreate_productTypeCreate_productType_taxType { diff --git a/src/productTypes/types/ProductTypeDelete.ts b/src/productTypes/types/ProductTypeDelete.ts index ea5c2a1b0..b3e87d735 100644 --- a/src/productTypes/types/ProductTypeDelete.ts +++ b/src/productTypes/types/ProductTypeDelete.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: ProductTypeDelete // ==================================================== export interface ProductTypeDelete_productTypeDelete_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface ProductTypeDelete_productTypeDelete_productType { diff --git a/src/productTypes/types/ProductTypeUpdate.ts b/src/productTypes/types/ProductTypeUpdate.ts index 58c66afe2..bd1156234 100644 --- a/src/productTypes/types/ProductTypeUpdate.ts +++ b/src/productTypes/types/ProductTypeUpdate.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { ProductTypeInput } from "./../../types/globalTypes"; +import { ProductTypeInput, ProductErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: ProductTypeUpdate // ==================================================== export interface ProductTypeUpdate_productTypeUpdate_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface ProductTypeUpdate_productTypeUpdate_productType_taxType { diff --git a/src/productTypes/types/UnassignAttribute.ts b/src/productTypes/types/UnassignAttribute.ts index b042fc6e3..631052cbe 100644 --- a/src/productTypes/types/UnassignAttribute.ts +++ b/src/productTypes/types/UnassignAttribute.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: UnassignAttribute // ==================================================== export interface UnassignAttribute_attributeUnassign_errors { - __typename: "Error"; + __typename: "ProductError"; + code: ProductErrorCode; field: string | null; - message: string | null; } export interface UnassignAttribute_attributeUnassign_productType_taxType { diff --git a/src/productTypes/views/ProductTypeCreate.tsx b/src/productTypes/views/ProductTypeCreate.tsx index f116699ca..3355ac13f 100644 --- a/src/productTypes/views/ProductTypeCreate.tsx +++ b/src/productTypes/views/ProductTypeCreate.tsx @@ -57,10 +57,9 @@ export const ProductTypeCreate: React.FC = () => { data.shop.defaultWeightUnit)} disabled={loading} - errors={maybe( - () => createProductTypeOpts.data.productTypeCreate.errors, - [] - )} + errors={ + createProductTypeOpts.data?.productTypeCreate.errors || [] + } pageTitle={intl.formatMessage({ defaultMessage: "Create Product Type", description: "header", diff --git a/src/productTypes/views/ProductTypeUpdate/errors.tsx b/src/productTypes/views/ProductTypeUpdate/errors.tsx index 5e2c83535..4eaa4ab40 100644 --- a/src/productTypes/views/ProductTypeUpdate/errors.tsx +++ b/src/productTypes/views/ProductTypeUpdate/errors.tsx @@ -1,19 +1,19 @@ import React from "react"; -import { UserError } from "../../../types"; +import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; interface ProductTypeUpdateErrorsState { - addAttributeErrors: UserError[]; - editAttributeErrors: UserError[]; - formErrors: UserError[]; + addAttributeErrors: ProductErrorFragment[]; + editAttributeErrors: ProductErrorFragment[]; + formErrors: ProductErrorFragment[]; } interface ProductTypeUpdateErrorsProps { children: (props: { errors: ProductTypeUpdateErrorsState; set: { - addAttributeErrors: (errors: UserError[]) => void; - editAttributeErrors: (errors: UserError[]) => void; - formErrors: (errors: UserError[]) => void; + addAttributeErrors: (errors: ProductErrorFragment[]) => void; + editAttributeErrors: (errors: ProductErrorFragment[]) => void; + formErrors: (errors: ProductErrorFragment[]) => void; }; }) => React.ReactNode; } @@ -32,11 +32,12 @@ export class ProductTypeUpdateErrors extends React.Component< return this.props.children({ errors: this.state, set: { - addAttributeErrors: (addAttributeErrors: UserError[]) => + addAttributeErrors: (addAttributeErrors: ProductErrorFragment[]) => this.setState({ addAttributeErrors }), - editAttributeErrors: (editAttributeErrors: UserError[]) => + editAttributeErrors: (editAttributeErrors: ProductErrorFragment[]) => this.setState({ editAttributeErrors }), - formErrors: (formErrors: UserError[]) => this.setState({ formErrors }) + formErrors: (formErrors: ProductErrorFragment[]) => + this.setState({ formErrors }) } }); } diff --git a/src/productTypes/views/ProductTypeUpdate/index.tsx b/src/productTypes/views/ProductTypeUpdate/index.tsx index 492320261..eaa9f99f2 100644 --- a/src/productTypes/views/ProductTypeUpdate/index.tsx +++ b/src/productTypes/views/ProductTypeUpdate/index.tsx @@ -319,13 +319,10 @@ export const ProductTypeUpdate: React.FC = ({ ) )} confirmButtonState={assignAttribute.opts.status} - errors={maybe( - () => - assignAttribute.opts.data.attributeAssign.errors.map( - err => err.message - ), - [] - )} + errors={ + assignAttribute.opts.data?.attributeAssign + .errors || [] + } loading={result.loading} onClose={closeModal} onSubmit={handleAssignAttribute} diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 5ced1da26..208db9898 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -10012,7 +10012,7 @@ exports[`Storyshots Views / Attributes / Attribute details form errors 1`] = `

- Generic form error + This field is required

- Generic form error + This field is required

- Generic form error + API error

- Generic form error + Unknown error

@@ -85147,7 +85147,7 @@ exports[`Storyshots Views / Product types / Product type details form errors 1`]

- Generic form error + Unknown error

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/storybook/stories/components/AssignAttributeDialog.tsx b/src/storybook/stories/components/AssignAttributeDialog.tsx index 9cb0462e5..2d75970fc 100644 --- a/src/storybook/stories/components/AssignAttributeDialog.tsx +++ b/src/storybook/stories/components/AssignAttributeDialog.tsx @@ -6,7 +6,7 @@ import { fetchMoreProps } from "@saleor/fixtures"; import AssignAttributeDialog, { AssignAttributeDialogProps } from "@saleor/productTypes/components/AssignAttributeDialog"; -import { formError } from "@saleor/storybook/misc"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import Decorator from "../../Decorator"; const props: AssignAttributeDialogProps = { @@ -30,5 +30,14 @@ storiesOf("Generics / Assign attributes dialog", module) )) .add("errors", () => ( - + )); diff --git a/src/storybook/stories/productTypes/ProductTypeAttributeEditDialog.tsx b/src/storybook/stories/productTypes/ProductTypeAttributeEditDialog.tsx deleted file mode 100644 index 8da8e9d68..000000000 --- a/src/storybook/stories/productTypes/ProductTypeAttributeEditDialog.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { storiesOf } from "@storybook/react"; -import React from "react"; - -import { formError } from "@saleor/storybook/misc"; -import ProductTypeAttributeEditDialog, { - ProductTypeAttributeEditDialogProps -} from "../../../productTypes/components/ProductTypeAttributeEditDialog"; -import { attributes } from "../../../productTypes/fixtures"; -import Decorator from "../../Decorator"; - -const attribute = attributes[0]; - -const props: ProductTypeAttributeEditDialogProps = { - disabled: false, - errors: [], - name: attribute.name, - onClose: () => undefined, - onConfirm: () => undefined, - opened: true, - title: "Add Attribute", - values: attribute.values.map(value => ({ - label: value.name, - value: value.id - })) -}; - -storiesOf("Product types / Edit attribute", module) - .addDecorator(Decorator) - .add("default", () => ) - .add("loading", () => ( - - )) - .add("form errors", () => ( - formError(field))} - /> - )); diff --git a/src/storybook/stories/productTypes/ProductTypeCreatePage.tsx b/src/storybook/stories/productTypes/ProductTypeCreatePage.tsx index e4e2b6ac0..e2cbe7064 100644 --- a/src/storybook/stories/productTypes/ProductTypeCreatePage.tsx +++ b/src/storybook/stories/productTypes/ProductTypeCreatePage.tsx @@ -2,12 +2,11 @@ import { Omit } from "@material-ui/core"; import { storiesOf } from "@storybook/react"; import React from "react"; -import { formError } from "@saleor/storybook/misc"; import ProductTypeCreatePage, { ProductTypeCreatePageProps, ProductTypeForm } from "../../../productTypes/components/ProductTypeCreatePage"; -import { WeightUnitsEnum } from "../../../types/globalTypes"; +import { WeightUnitsEnum, ProductErrorCode } from "../../../types/globalTypes"; import Decorator from "../../Decorator"; const props: Omit = { @@ -30,6 +29,10 @@ storiesOf("Views / Product types / Create product type", module) .add("form errors", () => ( ).map(formError)} + errors={(["name"] as Array).map(field => ({ + __typename: "ProductError", + code: ProductErrorCode.INVALID, + field + }))} /> )); diff --git a/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx b/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx index da2c83493..1a2065ba1 100644 --- a/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx +++ b/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx @@ -3,13 +3,12 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { listActionsProps } from "@saleor/fixtures"; -import { formError } from "@saleor/storybook/misc"; import ProductTypeDetailsPage, { ProductTypeDetailsPageProps, ProductTypeForm } from "../../../productTypes/components/ProductTypeDetailsPage"; import { productType } from "../../../productTypes/fixtures"; -import { WeightUnitsEnum } from "../../../types/globalTypes"; +import { WeightUnitsEnum, ProductErrorCode } from "../../../types/globalTypes"; import Decorator from "../../Decorator"; const props: Omit = { @@ -55,6 +54,12 @@ storiesOf("Views / Product types / Product type details", module) .add("form errors", () => ( ).map(formError)} + errors={(["name", "weight"] as Array).map( + field => ({ + __typename: "ProductError", + code: ProductErrorCode.INVALID, + field + }) + )} /> )); 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.ts b/src/utils/errors.ts deleted file mode 100644 index 9f8669d28..000000000 --- a/src/utils/errors.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { UserError } from "@saleor/types"; - -export function getFieldError(errors: UserError[], field: string): UserError { - return errors.find(err => err.field === field); -} - -export function getErrors(errors: UserError[]): string[] { - return errors - .filter(err => ["", null].includes(err.field)) - .map(err => err.message); -} 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/index.ts b/src/utils/errors/index.ts new file mode 100644 index 000000000..fc37f31bf --- /dev/null +++ b/src/utils/errors/index.ts @@ -0,0 +1,26 @@ +import { UserError } from "@saleor/types"; + +export function getFieldError( + errors: T[], + field: string +): T { + return errors.find(err => err.field === field); +} + +export function getErrors(errors: UserError[]): string[] { + return errors + .filter(err => ["", null].includes(err.field)) + .map(err => err.message); +} + +export function getFormErrors( + fields: TField[], + errors: TError[] +): Record { + return fields.reduce((errs, field) => { + errs[field] = getFieldError(errors, field); + return errs; + }, ({} as unknown) as Record); +} + +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;