From 63ff52349df9ac2d4d82f74839f57578c4f5043c Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Tue, 17 Mar 2020 19:49:01 +0100 Subject: [PATCH] Use error formatting in site settings view --- .../SiteSettingsAddress.tsx | 68 +++++++++++++------ .../SiteSettingsDetails.tsx | 21 +++--- .../SiteSettingsKeyDialog.tsx | 24 ++++--- .../SiteSettingsMailing.tsx | 34 +++++++--- .../SiteSettingsPage/SiteSettingsPage.tsx | 16 ++--- src/siteSettings/mutations.ts | 34 ++++++---- src/siteSettings/types/AuthorizationKeyAdd.ts | 6 +- .../types/AuthorizationKeyDelete.ts | 6 +- src/siteSettings/types/ShopErrorFragment.ts | 15 ++++ src/siteSettings/types/ShopSettingsUpdate.ts | 14 ++-- src/siteSettings/views/index.tsx | 48 +++++-------- src/utils/errors/shop.ts | 37 ++++++++++ 12 files changed, 203 insertions(+), 120 deletions(-) create mode 100644 src/siteSettings/types/ShopErrorFragment.ts create mode 100644 src/utils/errors/shop.ts diff --git a/src/siteSettings/components/SiteSettingsAddress/SiteSettingsAddress.tsx b/src/siteSettings/components/SiteSettingsAddress/SiteSettingsAddress.tsx index 8ab76f48a..bbf07bee7 100644 --- a/src/siteSettings/components/SiteSettingsAddress/SiteSettingsAddress.tsx +++ b/src/siteSettings/components/SiteSettingsAddress/SiteSettingsAddress.tsx @@ -3,7 +3,7 @@ import CardContent from "@material-ui/core/CardContent"; import { makeStyles } from "@material-ui/core/styles"; import TextField from "@material-ui/core/TextField"; import React from "react"; -import { useIntl } from "react-intl"; +import { useIntl, IntlShape } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; import FormSpacer from "@saleor/components/FormSpacer"; @@ -12,15 +12,18 @@ import SingleAutocompleteSelectField, { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; import { ChangeEvent } from "@saleor/hooks/useForm"; -import { UserError } from "@saleor/types"; -import { getFieldError } from "@saleor/utils/errors"; +import { getFormErrors } from "@saleor/utils/errors"; +import { ShopErrorFragment } from "@saleor/siteSettings/types/ShopErrorFragment"; +import getShopErrorMessage from "@saleor/utils/errors/shop"; +import { AccountErrorFragment } from "@saleor/customers/types/AccountErrorFragment"; +import getAccountErrorMessage from "@saleor/utils/errors/account"; import { SiteSettingsPageFormData } from "../SiteSettingsPage"; interface SiteSettingsAddressProps { countries: SingleAutocompleteChoiceType[]; data: SiteSettingsPageFormData; displayCountry: string; - errors: UserError[]; + errors: Array; disabled: boolean; onChange: (event: ChangeEvent) => void; onCountryChange: (event: ChangeEvent) => void; @@ -35,6 +38,17 @@ const useStyles = makeStyles( { name: "SiteSettingsAddress" } ); +function getErrorMessage( + err: AccountErrorFragment | ShopErrorFragment, + intl: IntlShape +): string { + if (err?.__typename === "AccountError") { + return getAccountErrorMessage(err, intl); + } + + return getShopErrorMessage(err, intl); +} + const SiteSettingsAddress: React.FC = props => { const { countries, @@ -45,10 +59,22 @@ const SiteSettingsAddress: React.FC = props => { onChange, onCountryChange } = props; - const classes = useStyles(props); + const classes = useStyles(props); const intl = useIntl(); + const formFields = [ + "companyName", + "streetAddress1", + "streetAddress2", + "city", + "postalCode", + "country", + "companyArea", + "phone" + ]; + const formErrors = getFormErrors(formFields, errors); + return ( = props => { = props => { = props => { = props => { = props => { /> = props => { = props => { /> = props => { ) => void; } @@ -26,6 +27,8 @@ const SiteSettingsDetails: React.FC = ({ }) => { const intl = useIntl(); + const formErrors = getFormErrors(["name", "domain", "description"], errors); + return ( = ({ = ({ void; } @@ -37,8 +39,10 @@ const SiteSettingsKeyDialog: React.FC = ({ }) => { const intl = useIntl(); + const formErrors = getFormErrors(["keyType", "key", "password"], errors); + return ( - +
{({ change, data }) => ( <> @@ -54,37 +58,37 @@ const SiteSettingsKeyDialog: React.FC = ({ label: authorizationKeyTypes[key], value: key }))} - error={!!getFieldError(errors, "keyType")} + error={!!formErrors.keyType} label={intl.formatMessage({ defaultMessage: "Authentication type", description: "authentication provider name" })} - hint={getFieldError(errors, "keyType")?.message} + hint={getShopErrorMessage(formErrors.keyType, intl)} name="type" onChange={change} value={data.type} /> ) => void; } @@ -41,9 +42,19 @@ const useStyles = makeStyles( const SiteSettingsMailing: React.FC = props => { const { data, disabled, errors, onChange } = props; + const classes = useStyles(props); const intl = useIntl(); + const formErrors = getFormErrors( + [ + "defaultMailSenderAddress", + "defaultMailSenderName", + "customerSetPasswordUrl" + ], + errors + ); + return ( = props => { = props => { = props => { defaultMessage: "URL address" })} helperText={ - getFieldError(errors, "customerSetPasswordUrl")?.message || + getShopErrorMessage(formErrors.customerSetPasswordUrl, intl) || intl.formatMessage({ defaultMessage: "This URL will be used as a main URL for password resets. It will be sent via email." diff --git a/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx b/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx index afa1f883e..dab35c263 100644 --- a/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx +++ b/src/siteSettings/components/SiteSettingsPage/SiteSettingsPage.tsx @@ -14,9 +14,9 @@ import SaveButtonBar from "@saleor/components/SaveButtonBar"; import useAddressValidation from "@saleor/hooks/useAddressValidation"; import useStateFromProps from "@saleor/hooks/useStateFromProps"; import { commonMessages, sectionNames } from "@saleor/intl"; -import { UserError } from "@saleor/types"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import { mapCountriesToChoices } from "@saleor/utils/maps"; +import { ShopErrorFragment } from "@saleor/siteSettings/types/ShopErrorFragment"; import { maybe } from "../../../misc"; import { AuthorizationKeyType } from "../../../types/globalTypes"; import { SiteSettings_shop } from "../../types/SiteSettings"; @@ -48,7 +48,7 @@ export interface SiteSettingsPageFormData export interface SiteSettingsPageProps { disabled: boolean; - errors: UserError[]; + errors: ShopErrorFragment[]; shop: SiteSettings_shop; saveButtonBarState: ConfirmButtonTransitionState; onBack: () => void; @@ -127,8 +127,6 @@ const SiteSettingsPage: React.FC = props => { name: maybe(() => shop.name, "") }; - const formErrors = [...errors, ...validationErrors]; - return ( = props => { confirmLeave > {({ change, data, hasChanged, submit }) => { - const countryChoices = mapCountriesToChoices( - maybe(() => shop.countries, []) - ); + const countryChoices = mapCountriesToChoices(shop?.countries || []); const handleCountryChange = createSingleAutocompleteSelectHandler( change, setDisplayCountry, @@ -169,7 +165,7 @@ const SiteSettingsPage: React.FC = props => { @@ -187,7 +183,7 @@ const SiteSettingsPage: React.FC = props => { @@ -208,7 +204,7 @@ const SiteSettingsPage: React.FC = props => { data={data} displayCountry={displayCountry} countries={countryChoices} - errors={formErrors} + errors={[...errors, ...validationErrors]} disabled={disabled} onChange={change} onCountryChange={handleCountryChange} diff --git a/src/siteSettings/mutations.ts b/src/siteSettings/mutations.ts index 09f7ef0f0..c13b45f7f 100644 --- a/src/siteSettings/mutations.ts +++ b/src/siteSettings/mutations.ts @@ -16,16 +16,22 @@ import { ShopSettingsUpdateVariables } from "./types/ShopSettingsUpdate"; +const shopErrorFragment = gql` + fragment ShopErrorFragment on ShopError { + code + field + } +`; const authorizationKeyAdd = gql` + ${shopErrorFragment} ${shopFragment} mutation AuthorizationKeyAdd( $input: AuthorizationKeyInput! $keyType: AuthorizationKeyType! ) { authorizationKeyAdd(input: $input, keyType: $keyType) { - errors { - field - message + errors: shopErrors { + ...ShopErrorFragment } shop { ...ShopFragment @@ -39,12 +45,12 @@ export const TypedAuthorizationKeyAdd = TypedMutation< >(authorizationKeyAdd); const authorizationKeyDelete = gql` + ${shopErrorFragment} ${shopFragment} mutation AuthorizationKeyDelete($keyType: AuthorizationKeyType!) { authorizationKeyDelete(keyType: $keyType) { - errors { - field - message + errors: shopErrors { + ...ShopErrorFragment } shop { ...ShopFragment @@ -58,6 +64,7 @@ export const TypedAuthorizationKeyDelete = TypedMutation< >(authorizationKeyDelete); const shopSettingsUpdate = gql` + ${shopErrorFragment} ${shopFragment} ${fragmentAddress} mutation ShopSettingsUpdate( @@ -66,18 +73,16 @@ const shopSettingsUpdate = gql` $addressInput: AddressInput ) { shopSettingsUpdate(input: $shopSettingsInput) { - errors { - field - message + errors: shopErrors { + ...ShopErrorFragment } shop { ...ShopFragment } } shopDomainUpdate(input: $shopDomainInput) { - errors { - field - message + errors: shopErrors { + ...ShopErrorFragment } shop { domain { @@ -87,9 +92,8 @@ const shopSettingsUpdate = gql` } } shopAddressUpdate(input: $addressInput) { - errors { - field - message + errors: shopErrors { + ...ShopErrorFragment } shop { companyAddress { diff --git a/src/siteSettings/types/AuthorizationKeyAdd.ts b/src/siteSettings/types/AuthorizationKeyAdd.ts index aa1b21e6f..258aabf71 100644 --- a/src/siteSettings/types/AuthorizationKeyAdd.ts +++ b/src/siteSettings/types/AuthorizationKeyAdd.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AuthorizationKeyInput, AuthorizationKeyType } from "./../../types/globalTypes"; +import { AuthorizationKeyInput, AuthorizationKeyType, ShopErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AuthorizationKeyAdd // ==================================================== export interface AuthorizationKeyAdd_authorizationKeyAdd_errors { - __typename: "Error"; + __typename: "ShopError"; + code: ShopErrorCode; field: string | null; - message: string | null; } export interface AuthorizationKeyAdd_authorizationKeyAdd_shop_authorizationKeys { diff --git a/src/siteSettings/types/AuthorizationKeyDelete.ts b/src/siteSettings/types/AuthorizationKeyDelete.ts index 7c9fcafea..ccf4f5ba8 100644 --- a/src/siteSettings/types/AuthorizationKeyDelete.ts +++ b/src/siteSettings/types/AuthorizationKeyDelete.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { AuthorizationKeyType } from "./../../types/globalTypes"; +import { AuthorizationKeyType, ShopErrorCode } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: AuthorizationKeyDelete // ==================================================== export interface AuthorizationKeyDelete_authorizationKeyDelete_errors { - __typename: "Error"; + __typename: "ShopError"; + code: ShopErrorCode; field: string | null; - message: string | null; } export interface AuthorizationKeyDelete_authorizationKeyDelete_shop_authorizationKeys { diff --git a/src/siteSettings/types/ShopErrorFragment.ts b/src/siteSettings/types/ShopErrorFragment.ts new file mode 100644 index 000000000..ae705f05f --- /dev/null +++ b/src/siteSettings/types/ShopErrorFragment.ts @@ -0,0 +1,15 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ShopErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL fragment: ShopErrorFragment +// ==================================================== + +export interface ShopErrorFragment { + __typename: "ShopError"; + code: ShopErrorCode; + field: string | null; +} diff --git a/src/siteSettings/types/ShopSettingsUpdate.ts b/src/siteSettings/types/ShopSettingsUpdate.ts index b7d30e12b..acc2e1bf1 100644 --- a/src/siteSettings/types/ShopSettingsUpdate.ts +++ b/src/siteSettings/types/ShopSettingsUpdate.ts @@ -2,16 +2,16 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { SiteDomainInput, ShopSettingsInput, AddressInput, AuthorizationKeyType } from "./../../types/globalTypes"; +import { SiteDomainInput, ShopSettingsInput, AddressInput, ShopErrorCode, AuthorizationKeyType } from "./../../types/globalTypes"; // ==================================================== // GraphQL mutation operation: ShopSettingsUpdate // ==================================================== export interface ShopSettingsUpdate_shopSettingsUpdate_errors { - __typename: "Error"; + __typename: "ShopError"; + code: ShopErrorCode; field: string | null; - message: string | null; } export interface ShopSettingsUpdate_shopSettingsUpdate_shop_authorizationKeys { @@ -73,9 +73,9 @@ export interface ShopSettingsUpdate_shopSettingsUpdate { } export interface ShopSettingsUpdate_shopDomainUpdate_errors { - __typename: "Error"; + __typename: "ShopError"; + code: ShopErrorCode; field: string | null; - message: string | null; } export interface ShopSettingsUpdate_shopDomainUpdate_shop_domain { @@ -96,9 +96,9 @@ export interface ShopSettingsUpdate_shopDomainUpdate { } export interface ShopSettingsUpdate_shopAddressUpdate_errors { - __typename: "Error"; + __typename: "ShopError"; + code: ShopErrorCode; field: string | null; - message: string | null; } export interface ShopSettingsUpdate_shopAddressUpdate_shop_companyAddress_country { diff --git a/src/siteSettings/views/index.tsx b/src/siteSettings/views/index.tsx index e8e104a69..9ae2347c4 100644 --- a/src/siteSettings/views/index.tsx +++ b/src/siteSettings/views/index.tsx @@ -7,7 +7,7 @@ import { commonMessages, sectionNames } from "@saleor/intl"; import { useIntl } from "react-intl"; import { configurationMenuUrl } from "../../configuration"; -import { findInEnum, maybe } from "../../misc"; +import { findInEnum } from "../../misc"; import { AuthorizationKeyType, CountryCode } from "../../types/globalTypes"; import SiteSettingsKeyDialog, { SiteSettingsKeyDialogForm @@ -37,7 +37,7 @@ export const SiteSettings: React.FC = ({ params }) => { const intl = useIntl(); const handleAddKeySuccess = (data: AuthorizationKeyAdd) => { - if (!maybe(() => data.authorizationKeyAdd.errors.length)) { + if (data.authorizationKeyAdd.errors.length === 0) { notify({ text: intl.formatMessage(commonMessages.savedChanges) }); @@ -45,7 +45,7 @@ export const SiteSettings: React.FC = ({ params }) => { } }; const handleDeleteKeySuccess = (data: AuthorizationKeyDelete) => { - if (!maybe(() => data.authorizationKeyDelete.errors.length)) { + if (data.authorizationKeyDelete.errors.length === 0) { notify({ text: intl.formatMessage(commonMessages.savedChanges) }); @@ -64,12 +64,9 @@ export const SiteSettings: React.FC = ({ params }) => { }; const handleSiteSettingsSuccess = (data: ShopSettingsUpdate) => { if ( - (!data.shopDomainUpdate.errors || - data.shopDomainUpdate.errors.length === 0) && - (!data.shopSettingsUpdate.errors || - data.shopSettingsUpdate.errors.length === 0) && - (!data.shopAddressUpdate.errors || - data.shopAddressUpdate.errors.length === 0) + data.shopDomainUpdate.errors.length === 0 && + data.shopSettingsUpdate.errors.length === 0 && + data.shopAddressUpdate.errors.length === 0 ) { notify({ text: intl.formatMessage(commonMessages.savedChanges) @@ -89,21 +86,12 @@ export const SiteSettings: React.FC = ({ params }) => { > {(updateShopSettings, updateShopSettingsOpts) => { const errors = [ - ...maybe( - () => - updateShopSettingsOpts.data.shopDomainUpdate.errors, - [] - ), - ...maybe( - () => - updateShopSettingsOpts.data.shopSettingsUpdate.errors, - [] - ), - ...maybe( - () => - updateShopSettingsOpts.data.shopAddressUpdate.errors, - [] - ) + ...(updateShopSettingsOpts.data?.shopDomainUpdate + .errors || []), + ...(updateShopSettingsOpts.data?.shopSettingsUpdate + .errors || []), + ...(updateShopSettingsOpts.data?.shopAddressUpdate + .errors || []) ]; const loading = siteSettings.loading || @@ -165,7 +153,7 @@ export const SiteSettings: React.FC = ({ params }) => { siteSettings.data.shop)} + shop={siteSettings.data?.shop} onBack={() => navigate(configurationMenuUrl)} onKeyAdd={() => navigate( @@ -183,12 +171,10 @@ export const SiteSettings: React.FC = ({ params }) => { saveButtonBarState={updateShopSettingsOpts.status} /> - addAuthorizationKeyOpts.data.authorizationKeyAdd - .errors, - [] - )} + errors={ + addAuthorizationKeyOpts.data?.authorizationKeyAdd + .errors || [] + } initial={{ key: "", password: "", diff --git a/src/utils/errors/shop.ts b/src/utils/errors/shop.ts new file mode 100644 index 000000000..7e74f7843 --- /dev/null +++ b/src/utils/errors/shop.ts @@ -0,0 +1,37 @@ +import { IntlShape, defineMessages } from "react-intl"; + +import { ShopErrorFragment } from "@saleor/siteSettings/types/ShopErrorFragment"; +import { ShopErrorCode } from "@saleor/types/globalTypes"; +import { commonMessages } from "@saleor/intl"; +import commonErrorMessages from "./common"; + +const messages = defineMessages({ + alreadyExists: { + defaultMessage: "Authorization key with this type already exists", + description: "add authorization key error" + } +}); + +function getShopErrorMessage( + err: Omit | undefined, + intl: IntlShape +): string { + if (err) { + switch (err.code) { + case ShopErrorCode.ALREADY_EXISTS: + return intl.formatMessage(messages.alreadyExists); + case ShopErrorCode.GRAPHQL_ERROR: + return intl.formatMessage(commonErrorMessages.graphqlError); + case ShopErrorCode.REQUIRED: + return intl.formatMessage(commonMessages.requiredField); + case ShopErrorCode.INVALID: + return intl.formatMessage(commonErrorMessages.invalid); + default: + return intl.formatMessage(commonErrorMessages.unknownError); + } + } + + return undefined; +} + +export default getShopErrorMessage;