From f2af4a1b2301a025ca781326ad7f13b89cabb5fd Mon Sep 17 00:00:00 2001 From: Dawid Date: Wed, 30 Nov 2022 17:18:44 +0100 Subject: [PATCH] Metadata settings in tax classes (#2680) * feat: added metadata settings to tax classes * test: update test snapshots * refactor: update taxes error handling * refactor: add TaxClassBase fragment * refactor: update tax classes initial values definition --- src/fragments/errors.ts | 18 +- src/fragments/taxes.ts | 11 +- src/graphql/hooks.generated.ts | 68 ++-- src/graphql/types.generated.ts | 30 +- .../ProductTypeCreatePage.tsx | 4 +- .../ProductTypeDetailsPage.tsx | 4 +- .../ProductTypeTaxes/ProductTypeTaxes.tsx | 4 +- src/productTypes/handlers.ts | 4 +- .../ProductCreatePage/ProductCreatePage.tsx | 4 +- .../components/ProductTaxes/ProductTaxes.tsx | 4 +- .../ProductUpdatePage/ProductUpdatePage.tsx | 4 +- .../ShippingMethodTaxes.tsx | 4 +- .../ShippingZoneRatesCreatePage.tsx | 4 +- .../ShippingZoneRatesPage.tsx | 4 +- .../__snapshots__/Stories.test.ts.snap | 296 ++++++++++++++++++ src/taxes/fixtures.ts | 4 + src/taxes/mutations.ts | 12 +- .../pages/TaxClassesPage/TaxClassesPage.tsx | 22 +- src/taxes/pages/TaxClassesPage/form.tsx | 118 +++---- src/taxes/types.ts | 9 + src/taxes/utils/data.ts | 59 ++++ src/taxes/utils/useTaxClassFetchMore.ts | 4 +- src/taxes/utils/validation.ts | 21 ++ src/taxes/views/TaxClassesList.tsx | 80 ++++- src/utils/errors/account.ts | 1 + src/utils/errors/common.ts | 13 +- src/utils/errors/taxes.ts | 27 ++ src/utils/handlers/metadataCreateHandler.ts | 5 + 28 files changed, 679 insertions(+), 159 deletions(-) create mode 100644 src/taxes/types.ts create mode 100644 src/taxes/utils/data.ts create mode 100644 src/taxes/utils/validation.ts create mode 100644 src/utils/errors/taxes.ts diff --git a/src/fragments/errors.ts b/src/fragments/errors.ts index 72d9de8a2..50761f8bd 100644 --- a/src/fragments/errors.ts +++ b/src/fragments/errors.ts @@ -489,40 +489,46 @@ export const shippingPriceTranslateErrorFragment = gql` `; export const taxConfigurationUpdateError = gql` - fragment TaxConfigurationUpdateErrorFragment on TaxConfigurationUpdateError { + fragment TaxConfigurationUpdateError on TaxConfigurationUpdateError { field code + message } `; export const taxCountryConfigurationUpdateError = gql` - fragment TaxCountryConfigurationUpdateErrorFragment on TaxCountryConfigurationUpdateError { + fragment TaxCountryConfigurationUpdateError on TaxCountryConfigurationUpdateError { field code + message } `; export const taxCountryConfigurationDeleteError = gql` - fragment TaxCountryConfigurationDeleteErrorFragment on TaxCountryConfigurationDeleteError { + fragment TaxCountryConfigurationDeleteError on TaxCountryConfigurationDeleteError { field code + message } `; export const taxClassUpdateError = gql` - fragment TaxClassUpdateErrorFragment on TaxClassUpdateError { + fragment TaxClassUpdateError on TaxClassUpdateError { field code + message } `; export const taxClassCreateError = gql` - fragment TaxClassCreateErrorFragment on TaxClassCreateError { + fragment TaxClassCreateError on TaxClassCreateError { field code + message } `; export const taxClassDeleteError = gql` - fragment TaxClassDeleteErrorFragment on TaxClassDeleteError { + fragment TaxClassDeleteError on TaxClassDeleteError { field code + message } `; diff --git a/src/fragments/taxes.ts b/src/fragments/taxes.ts index 982d77efe..8477ef03e 100644 --- a/src/fragments/taxes.ts +++ b/src/fragments/taxes.ts @@ -69,12 +69,19 @@ export const taxRateFragment = gql` } `; -export const taxClassFragment = gql` - fragment TaxClass on TaxClass { +export const taxClassBaseFragment = gql` + fragment TaxClassBase on TaxClass { id name + } +`; + +export const taxClassFragment = gql` + fragment TaxClass on TaxClass { + ...TaxClassBase countries { ...TaxRate } + ...Metadata } `; diff --git a/src/graphql/hooks.generated.ts b/src/graphql/hooks.generated.ts index dcb20dccf..2f0d1d0e4 100644 --- a/src/graphql/hooks.generated.ts +++ b/src/graphql/hooks.generated.ts @@ -972,40 +972,46 @@ export const ShippingPriceTranslateErrorFragmentFragmentDoc = gql` message } `; -export const TaxConfigurationUpdateErrorFragmentFragmentDoc = gql` - fragment TaxConfigurationUpdateErrorFragment on TaxConfigurationUpdateError { +export const TaxConfigurationUpdateErrorFragmentDoc = gql` + fragment TaxConfigurationUpdateError on TaxConfigurationUpdateError { field code + message } `; -export const TaxCountryConfigurationUpdateErrorFragmentFragmentDoc = gql` - fragment TaxCountryConfigurationUpdateErrorFragment on TaxCountryConfigurationUpdateError { +export const TaxCountryConfigurationUpdateErrorFragmentDoc = gql` + fragment TaxCountryConfigurationUpdateError on TaxCountryConfigurationUpdateError { field code + message } `; -export const TaxCountryConfigurationDeleteErrorFragmentFragmentDoc = gql` - fragment TaxCountryConfigurationDeleteErrorFragment on TaxCountryConfigurationDeleteError { +export const TaxCountryConfigurationDeleteErrorFragmentDoc = gql` + fragment TaxCountryConfigurationDeleteError on TaxCountryConfigurationDeleteError { field code + message } `; -export const TaxClassUpdateErrorFragmentFragmentDoc = gql` - fragment TaxClassUpdateErrorFragment on TaxClassUpdateError { +export const TaxClassUpdateErrorFragmentDoc = gql` + fragment TaxClassUpdateError on TaxClassUpdateError { field code + message } `; -export const TaxClassCreateErrorFragmentFragmentDoc = gql` - fragment TaxClassCreateErrorFragment on TaxClassCreateError { +export const TaxClassCreateErrorFragmentDoc = gql` + fragment TaxClassCreateError on TaxClassCreateError { field code + message } `; -export const TaxClassDeleteErrorFragmentFragmentDoc = gql` - fragment TaxClassDeleteErrorFragment on TaxClassDeleteError { +export const TaxClassDeleteErrorFragmentDoc = gql` + fragment TaxClassDeleteError on TaxClassDeleteError { field code + message } `; export const GiftCardsSettingsFragmentDoc = gql` @@ -2440,6 +2446,12 @@ export const TaxCountryConfigurationFragmentDoc = gql` } } ${CountryWithCodeFragmentDoc}`; +export const TaxClassBaseFragmentDoc = gql` + fragment TaxClassBase on TaxClass { + id + name +} + `; export const TaxRateFragmentDoc = gql` fragment TaxRate on TaxClassCountryRate { country { @@ -2450,13 +2462,15 @@ export const TaxRateFragmentDoc = gql` ${CountryWithCodeFragmentDoc}`; export const TaxClassFragmentDoc = gql` fragment TaxClass on TaxClass { - id - name + ...TaxClassBase countries { ...TaxRate } + ...Metadata } - ${TaxRateFragmentDoc}`; + ${TaxClassBaseFragmentDoc} +${TaxRateFragmentDoc} +${MetadataFragmentDoc}`; export const TimePeriodFragmentDoc = gql` fragment TimePeriod on TimePeriod { amount @@ -14767,14 +14781,14 @@ export const TaxConfigurationUpdateDocument = gql` mutation TaxConfigurationUpdate($id: ID!, $input: TaxConfigurationUpdateInput!) { taxConfigurationUpdate(id: $id, input: $input) { errors { - ...TaxConfigurationUpdateErrorFragment + ...TaxConfigurationUpdateError } taxConfiguration { ...TaxConfiguration } } } - ${TaxConfigurationUpdateErrorFragmentFragmentDoc} + ${TaxConfigurationUpdateErrorFragmentDoc} ${TaxConfigurationFragmentDoc}`; export type TaxConfigurationUpdateMutationFn = Apollo.MutationFunction; @@ -14810,14 +14824,14 @@ export const TaxCountryConfigurationUpdateDocument = gql` updateTaxClassRates: $updateTaxClassRates ) { errors { - ...TaxCountryConfigurationUpdateErrorFragment + ...TaxCountryConfigurationUpdateError } taxCountryConfiguration { ...TaxCountryConfiguration } } } - ${TaxCountryConfigurationUpdateErrorFragmentFragmentDoc} + ${TaxCountryConfigurationUpdateErrorFragmentDoc} ${TaxCountryConfigurationFragmentDoc}`; export type TaxCountryConfigurationUpdateMutationFn = Apollo.MutationFunction; @@ -14850,14 +14864,14 @@ export const TaxCountryConfigurationDeleteDocument = gql` mutation TaxCountryConfigurationDelete($countryCode: CountryCode!) { taxCountryConfigurationDelete(countryCode: $countryCode) { errors { - ...TaxCountryConfigurationDeleteErrorFragment + ...TaxCountryConfigurationDeleteError } taxCountryConfiguration { ...TaxCountryConfiguration } } } - ${TaxCountryConfigurationDeleteErrorFragmentFragmentDoc} + ${TaxCountryConfigurationDeleteErrorFragmentDoc} ${TaxCountryConfigurationFragmentDoc}`; export type TaxCountryConfigurationDeleteMutationFn = Apollo.MutationFunction; @@ -14889,14 +14903,14 @@ export const TaxClassUpdateDocument = gql` mutation TaxClassUpdate($id: ID!, $input: TaxClassUpdateInput!) { taxClassUpdate(id: $id, input: $input) { errors { - ...TaxClassUpdateErrorFragment + ...TaxClassUpdateError } taxClass { ...TaxClass } } } - ${TaxClassUpdateErrorFragmentFragmentDoc} + ${TaxClassUpdateErrorFragmentDoc} ${TaxClassFragmentDoc}`; export type TaxClassUpdateMutationFn = Apollo.MutationFunction; @@ -14929,14 +14943,14 @@ export const TaxClassCreateDocument = gql` mutation TaxClassCreate($input: TaxClassCreateInput!) { taxClassCreate(input: $input) { errors { - ...TaxClassCreateErrorFragment + ...TaxClassCreateError } taxClass { ...TaxClass } } } - ${TaxClassCreateErrorFragmentFragmentDoc} + ${TaxClassCreateErrorFragmentDoc} ${TaxClassFragmentDoc}`; export type TaxClassCreateMutationFn = Apollo.MutationFunction; @@ -14968,11 +14982,11 @@ export const TaxClassDeleteDocument = gql` mutation TaxClassDelete($id: ID!) { taxClassDelete(id: $id) { errors { - ...TaxClassDeleteErrorFragment + ...TaxClassDeleteError } } } - ${TaxClassDeleteErrorFragmentFragmentDoc}`; + ${TaxClassDeleteErrorFragmentDoc}`; export type TaxClassDeleteMutationFn = Apollo.MutationFunction; /** diff --git a/src/graphql/types.generated.ts b/src/graphql/types.generated.ts index 5cd10dc04..6fd9e42cb 100644 --- a/src/graphql/types.generated.ts +++ b/src/graphql/types.generated.ts @@ -7189,17 +7189,17 @@ export type AttributeValueTranslateErrorFragmentFragment = { __typename: 'Transl export type ShippingPriceTranslateErrorFragmentFragment = { __typename: 'TranslationError', code: TranslationErrorCode, field: string | null, message: string | null }; -export type TaxConfigurationUpdateErrorFragmentFragment = { __typename: 'TaxConfigurationUpdateError', field: string | null, code: TaxConfigurationUpdateErrorCode }; +export type TaxConfigurationUpdateErrorFragment = { __typename: 'TaxConfigurationUpdateError', field: string | null, code: TaxConfigurationUpdateErrorCode, message: string | null }; -export type TaxCountryConfigurationUpdateErrorFragmentFragment = { __typename: 'TaxCountryConfigurationUpdateError', field: string | null, code: TaxCountryConfigurationUpdateErrorCode }; +export type TaxCountryConfigurationUpdateErrorFragment = { __typename: 'TaxCountryConfigurationUpdateError', field: string | null, code: TaxCountryConfigurationUpdateErrorCode, message: string | null }; -export type TaxCountryConfigurationDeleteErrorFragmentFragment = { __typename: 'TaxCountryConfigurationDeleteError', field: string | null, code: TaxCountryConfigurationDeleteErrorCode }; +export type TaxCountryConfigurationDeleteErrorFragment = { __typename: 'TaxCountryConfigurationDeleteError', field: string | null, code: TaxCountryConfigurationDeleteErrorCode, message: string | null }; -export type TaxClassUpdateErrorFragmentFragment = { __typename: 'TaxClassUpdateError', field: string | null, code: TaxClassUpdateErrorCode }; +export type TaxClassUpdateErrorFragment = { __typename: 'TaxClassUpdateError', field: string | null, code: TaxClassUpdateErrorCode, message: string | null }; -export type TaxClassCreateErrorFragmentFragment = { __typename: 'TaxClassCreateError', field: string | null, code: TaxClassCreateErrorCode }; +export type TaxClassCreateErrorFragment = { __typename: 'TaxClassCreateError', field: string | null, code: TaxClassCreateErrorCode, message: string | null }; -export type TaxClassDeleteErrorFragmentFragment = { __typename: 'TaxClassDeleteError', field: string | null, code: TaxClassDeleteErrorCode }; +export type TaxClassDeleteErrorFragment = { __typename: 'TaxClassDeleteError', field: string | null, code: TaxClassDeleteErrorCode, message: string | null }; export type FileFragment = { __typename: 'File', url: string, contentType: string | null }; @@ -7409,7 +7409,9 @@ export type TaxCountryConfigurationFragment = { __typename: 'TaxCountryConfigura export type TaxRateFragment = { __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }; -export type TaxClassFragment = { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }> }; +export type TaxClassBaseFragment = { __typename: 'TaxClass', id: string, name: string }; + +export type TaxClassFragment = { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> }; export type TimePeriodFragment = { __typename: 'TimePeriod', amount: number, type: TimePeriodTypeEnum }; @@ -8975,7 +8977,7 @@ export type TaxConfigurationUpdateMutationVariables = Exact<{ }>; -export type TaxConfigurationUpdateMutation = { __typename: 'Mutation', taxConfigurationUpdate: { __typename: 'TaxConfigurationUpdate', errors: Array<{ __typename: 'TaxConfigurationUpdateError', field: string | null, code: TaxConfigurationUpdateErrorCode }>, taxConfiguration: { __typename: 'TaxConfiguration', id: string, displayGrossPrices: boolean, pricesEnteredWithTax: boolean, chargeTaxes: boolean, taxCalculationStrategy: TaxCalculationStrategy | null, channel: { __typename: 'Channel', id: string, name: string }, countries: Array<{ __typename: 'TaxConfigurationPerCountry', chargeTaxes: boolean, taxCalculationStrategy: TaxCalculationStrategy | null, displayGrossPrices: boolean, country: { __typename: 'CountryDisplay', country: string, code: string } }> } | null } | null }; +export type TaxConfigurationUpdateMutation = { __typename: 'Mutation', taxConfigurationUpdate: { __typename: 'TaxConfigurationUpdate', errors: Array<{ __typename: 'TaxConfigurationUpdateError', field: string | null, code: TaxConfigurationUpdateErrorCode, message: string | null }>, taxConfiguration: { __typename: 'TaxConfiguration', id: string, displayGrossPrices: boolean, pricesEnteredWithTax: boolean, chargeTaxes: boolean, taxCalculationStrategy: TaxCalculationStrategy | null, channel: { __typename: 'Channel', id: string, name: string }, countries: Array<{ __typename: 'TaxConfigurationPerCountry', chargeTaxes: boolean, taxCalculationStrategy: TaxCalculationStrategy | null, displayGrossPrices: boolean, country: { __typename: 'CountryDisplay', country: string, code: string } }> } | null } | null }; export type TaxCountryConfigurationUpdateMutationVariables = Exact<{ countryCode: CountryCode; @@ -8983,14 +8985,14 @@ export type TaxCountryConfigurationUpdateMutationVariables = Exact<{ }>; -export type TaxCountryConfigurationUpdateMutation = { __typename: 'Mutation', taxCountryConfigurationUpdate: { __typename: 'TaxCountryConfigurationUpdate', errors: Array<{ __typename: 'TaxCountryConfigurationUpdateError', field: string | null, code: TaxCountryConfigurationUpdateErrorCode }>, taxCountryConfiguration: { __typename: 'TaxCountryConfiguration', country: { __typename: 'CountryDisplay', country: string, code: string }, taxClassCountryRates: Array<{ __typename: 'TaxClassCountryRate', rate: number, taxClass: { __typename: 'TaxClass', id: string, name: string } | null }> } | null } | null }; +export type TaxCountryConfigurationUpdateMutation = { __typename: 'Mutation', taxCountryConfigurationUpdate: { __typename: 'TaxCountryConfigurationUpdate', errors: Array<{ __typename: 'TaxCountryConfigurationUpdateError', field: string | null, code: TaxCountryConfigurationUpdateErrorCode, message: string | null }>, taxCountryConfiguration: { __typename: 'TaxCountryConfiguration', country: { __typename: 'CountryDisplay', country: string, code: string }, taxClassCountryRates: Array<{ __typename: 'TaxClassCountryRate', rate: number, taxClass: { __typename: 'TaxClass', id: string, name: string } | null }> } | null } | null }; export type TaxCountryConfigurationDeleteMutationVariables = Exact<{ countryCode: CountryCode; }>; -export type TaxCountryConfigurationDeleteMutation = { __typename: 'Mutation', taxCountryConfigurationDelete: { __typename: 'TaxCountryConfigurationDelete', errors: Array<{ __typename: 'TaxCountryConfigurationDeleteError', field: string | null, code: TaxCountryConfigurationDeleteErrorCode }>, taxCountryConfiguration: { __typename: 'TaxCountryConfiguration', country: { __typename: 'CountryDisplay', country: string, code: string }, taxClassCountryRates: Array<{ __typename: 'TaxClassCountryRate', rate: number, taxClass: { __typename: 'TaxClass', id: string, name: string } | null }> } | null } | null }; +export type TaxCountryConfigurationDeleteMutation = { __typename: 'Mutation', taxCountryConfigurationDelete: { __typename: 'TaxCountryConfigurationDelete', errors: Array<{ __typename: 'TaxCountryConfigurationDeleteError', field: string | null, code: TaxCountryConfigurationDeleteErrorCode, message: string | null }>, taxCountryConfiguration: { __typename: 'TaxCountryConfiguration', country: { __typename: 'CountryDisplay', country: string, code: string }, taxClassCountryRates: Array<{ __typename: 'TaxClassCountryRate', rate: number, taxClass: { __typename: 'TaxClass', id: string, name: string } | null }> } | null } | null }; export type TaxClassUpdateMutationVariables = Exact<{ id: Scalars['ID']; @@ -8998,21 +9000,21 @@ export type TaxClassUpdateMutationVariables = Exact<{ }>; -export type TaxClassUpdateMutation = { __typename: 'Mutation', taxClassUpdate: { __typename: 'TaxClassUpdate', errors: Array<{ __typename: 'TaxClassUpdateError', field: string | null, code: TaxClassUpdateErrorCode }>, taxClass: { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }> } | null } | null }; +export type TaxClassUpdateMutation = { __typename: 'Mutation', taxClassUpdate: { __typename: 'TaxClassUpdate', errors: Array<{ __typename: 'TaxClassUpdateError', field: string | null, code: TaxClassUpdateErrorCode, message: string | null }>, taxClass: { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> } | null } | null }; export type TaxClassCreateMutationVariables = Exact<{ input: TaxClassCreateInput; }>; -export type TaxClassCreateMutation = { __typename: 'Mutation', taxClassCreate: { __typename: 'TaxClassCreate', errors: Array<{ __typename: 'TaxClassCreateError', field: string | null, code: TaxClassCreateErrorCode }>, taxClass: { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }> } | null } | null }; +export type TaxClassCreateMutation = { __typename: 'Mutation', taxClassCreate: { __typename: 'TaxClassCreate', errors: Array<{ __typename: 'TaxClassCreateError', field: string | null, code: TaxClassCreateErrorCode, message: string | null }>, taxClass: { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> } | null } | null }; export type TaxClassDeleteMutationVariables = Exact<{ id: Scalars['ID']; }>; -export type TaxClassDeleteMutation = { __typename: 'Mutation', taxClassDelete: { __typename: 'TaxClassDelete', errors: Array<{ __typename: 'TaxClassDeleteError', field: string | null, code: TaxClassDeleteErrorCode }> } | null }; +export type TaxClassDeleteMutation = { __typename: 'Mutation', taxClassDelete: { __typename: 'TaxClassDelete', errors: Array<{ __typename: 'TaxClassDeleteError', field: string | null, code: TaxClassDeleteErrorCode, message: string | null }> } | null }; export type TaxConfigurationsListQueryVariables = Exact<{ before?: InputMaybe; @@ -9040,7 +9042,7 @@ export type TaxClassesListQueryVariables = Exact<{ }>; -export type TaxClassesListQuery = { __typename: 'Query', taxClasses: { __typename: 'TaxClassCountableConnection', edges: Array<{ __typename: 'TaxClassCountableEdge', node: { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }> } }> } | null }; +export type TaxClassesListQuery = { __typename: 'Query', taxClasses: { __typename: 'TaxClassCountableConnection', edges: Array<{ __typename: 'TaxClassCountableEdge', node: { __typename: 'TaxClass', id: string, name: string, countries: Array<{ __typename: 'TaxClassCountryRate', rate: number, country: { __typename: 'CountryDisplay', country: string, code: string } }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> } }> } | null }; export type TaxClassAssignQueryVariables = Exact<{ first?: InputMaybe; diff --git a/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx b/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx index 20b8f86cb..23cee8688 100644 --- a/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx +++ b/src/productTypes/components/ProductTypeCreatePage/ProductTypeCreatePage.tsx @@ -8,7 +8,7 @@ import PageHeader from "@saleor/components/PageHeader"; import Savebar from "@saleor/components/Savebar"; import { ProductTypeKindEnum, - TaxClassFragment, + TaxClassBaseFragment, WeightUnitsEnum, } from "@saleor/graphql"; import { SubmitPromise } from "@saleor/hooks/useForm"; @@ -44,7 +44,7 @@ export interface ProductTypeCreatePageProps { disabled: boolean; pageTitle: string; saveButtonBarState: ConfirmButtonTransitionState; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; kind: ProductTypeKindEnum; onChangeKind: (kind: ProductTypeKindEnum) => void; onSubmit: (data: ProductTypeForm) => SubmitPromise; diff --git a/src/productTypes/components/ProductTypeDetailsPage/ProductTypeDetailsPage.tsx b/src/productTypes/components/ProductTypeDetailsPage/ProductTypeDetailsPage.tsx index e2bb78d2f..d0418234c 100644 --- a/src/productTypes/components/ProductTypeDetailsPage/ProductTypeDetailsPage.tsx +++ b/src/productTypes/components/ProductTypeDetailsPage/ProductTypeDetailsPage.tsx @@ -12,7 +12,7 @@ import { ProductAttributeType, ProductTypeDetailsQuery, ProductTypeKindEnum, - TaxClassFragment, + TaxClassBaseFragment, WeightUnitsEnum, } from "@saleor/graphql"; import { SubmitPromise } from "@saleor/hooks/useForm"; @@ -64,7 +64,7 @@ export interface ProductTypeDetailsPageProps { pageTitle: string; productAttributeList: ListActions; saveButtonBarState: ConfirmButtonTransitionState; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; variantAttributeList: ListActions; onAttributeAdd: (type: ProductAttributeType) => void; onAttributeReorder: (event: ReorderEvent, type: ProductAttributeType) => void; diff --git a/src/productTypes/components/ProductTypeTaxes/ProductTypeTaxes.tsx b/src/productTypes/components/ProductTypeTaxes/ProductTypeTaxes.tsx index bbd45eb59..59d027a04 100644 --- a/src/productTypes/components/ProductTypeTaxes/ProductTypeTaxes.tsx +++ b/src/productTypes/components/ProductTypeTaxes/ProductTypeTaxes.tsx @@ -1,7 +1,7 @@ import { Card, CardContent } from "@material-ui/core"; import CardTitle from "@saleor/components/CardTitle"; import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField"; -import { TaxClassFragment } from "@saleor/graphql"; +import { TaxClassBaseFragment } from "@saleor/graphql"; import { sectionNames } from "@saleor/intl"; import { makeStyles } from "@saleor/macaw-ui"; import { taxesMessages } from "@saleor/taxes/messages"; @@ -16,7 +16,7 @@ interface ProductTypeTaxesProps { taxClassId: string; }; taxClassDisplayName: string; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; disabled: boolean; onChange: (event: React.ChangeEvent) => void; onFetchMore: FetchMoreProps; diff --git a/src/productTypes/handlers.ts b/src/productTypes/handlers.ts index f52c07a37..3584e20bb 100644 --- a/src/productTypes/handlers.ts +++ b/src/productTypes/handlers.ts @@ -1,4 +1,4 @@ -import { ProductTypeKindEnum, TaxClassFragment } from "@saleor/graphql"; +import { ProductTypeKindEnum, TaxClassBaseFragment } from "@saleor/graphql"; import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; export const makeProductTypeKindChangeHandler = ( @@ -12,7 +12,7 @@ export const makeProductTypeKindChangeHandler = ( export function handleTaxClassChange( event: ChangeEvent, - taxClasses: Array>, + taxClasses: TaxClassBaseFragment[], formChange: FormChange, displayChange: (name: string) => void, ) { diff --git a/src/products/components/ProductCreatePage/ProductCreatePage.tsx b/src/products/components/ProductCreatePage/ProductCreatePage.tsx index 72cb8a3c0..4c30f8fb6 100644 --- a/src/products/components/ProductCreatePage/ProductCreatePage.tsx +++ b/src/products/components/ProductCreatePage/ProductCreatePage.tsx @@ -28,7 +28,7 @@ import { SearchProductsQuery, SearchProductTypesQuery, SearchWarehousesQuery, - TaxClassFragment, + TaxClassBaseFragment, } from "@saleor/graphql"; import useNavigator from "@saleor/hooks/useNavigator"; import useStateFromProps from "@saleor/hooks/useStateFromProps"; @@ -75,7 +75,7 @@ interface ProductCreatePageProps { saveButtonBarState: ConfirmButtonTransitionState; weightUnit: string; warehouses: RelayToFlat; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; fetchMoreTaxClasses: FetchMoreProps; selectedProductType?: ProductTypeQuery["productType"]; fetchCategories: (data: string) => void; diff --git a/src/products/components/ProductTaxes/ProductTaxes.tsx b/src/products/components/ProductTaxes/ProductTaxes.tsx index 7ce859dd5..0140f760b 100644 --- a/src/products/components/ProductTaxes/ProductTaxes.tsx +++ b/src/products/components/ProductTaxes/ProductTaxes.tsx @@ -1,7 +1,7 @@ import { Card, CardContent } from "@material-ui/core"; import CardTitle from "@saleor/components/CardTitle"; import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField"; -import { TaxClassFragment } from "@saleor/graphql"; +import { TaxClassBaseFragment } from "@saleor/graphql"; import { sectionNames } from "@saleor/intl"; import { makeStyles } from "@saleor/macaw-ui"; import { taxesMessages } from "@saleor/taxes/messages"; @@ -14,7 +14,7 @@ import { ProductCreateFormData } from "../ProductCreatePage"; interface ProductTaxesProps { value: string; taxClassDisplayName: string; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; disabled: boolean; onChange: (event: React.ChangeEvent) => void; onFetchMore: FetchMoreProps; diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index e41333c77..9fd4e1970 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -35,7 +35,7 @@ import { SearchCollectionsQuery, SearchPagesQuery, SearchProductsQuery, - TaxClassFragment, + TaxClassBaseFragment, WarehouseFragment, } from "@saleor/graphql"; import { SubmitPromise } from "@saleor/hooks/useForm"; @@ -89,7 +89,7 @@ export interface ProductUpdatePageProps { header: string; saveButtonBarState: ConfirmButtonTransitionState; warehouses: WarehouseFragment[]; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; fetchMoreTaxClasses: FetchMoreProps; referencePages?: RelayToFlat; referenceProducts?: RelayToFlat; diff --git a/src/shipping/components/ShippingMethodTaxes/ShippingMethodTaxes.tsx b/src/shipping/components/ShippingMethodTaxes/ShippingMethodTaxes.tsx index ecf03953d..3f599a13a 100644 --- a/src/shipping/components/ShippingMethodTaxes/ShippingMethodTaxes.tsx +++ b/src/shipping/components/ShippingMethodTaxes/ShippingMethodTaxes.tsx @@ -1,7 +1,7 @@ import { Card, CardContent } from "@material-ui/core"; import CardTitle from "@saleor/components/CardTitle"; import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField"; -import { TaxClassFragment } from "@saleor/graphql"; +import { TaxClassBaseFragment } from "@saleor/graphql"; import { sectionNames } from "@saleor/intl"; import { makeStyles } from "@saleor/macaw-ui"; import { taxesMessages } from "@saleor/taxes/messages"; @@ -14,7 +14,7 @@ import { ShippingZoneRateUpdateFormData } from "../ShippingZoneRatesPage/types"; interface ShippingMethodTaxesProps { value: string; taxClassDisplayName: string; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; disabled: boolean; onChange: (event: React.ChangeEvent) => void; onFetchMore: FetchMoreProps; diff --git a/src/shipping/components/ShippingZoneRatesCreatePage/ShippingZoneRatesCreatePage.tsx b/src/shipping/components/ShippingZoneRatesCreatePage/ShippingZoneRatesCreatePage.tsx index 1b7565826..e4dcd9865 100644 --- a/src/shipping/components/ShippingZoneRatesCreatePage/ShippingZoneRatesCreatePage.tsx +++ b/src/shipping/components/ShippingZoneRatesCreatePage/ShippingZoneRatesCreatePage.tsx @@ -14,7 +14,7 @@ import { ShippingErrorFragment, ShippingMethodTypeEnum, ShippingMethodTypeFragment, - TaxClassFragment, + TaxClassBaseFragment, } from "@saleor/graphql"; import useForm, { SubmitPromise } from "@saleor/hooks/useForm"; import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit"; @@ -56,7 +56,7 @@ export interface ShippingZoneRatesCreatePageProps extends WithFormId { onChannelsChange: (data: ChannelShippingData[]) => void; openChannelsModal: () => void; variant: ShippingMethodTypeEnum; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; fetchMoreTaxClasses: FetchMoreProps; } diff --git a/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx b/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx index 3e3683542..daa81943e 100644 --- a/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx +++ b/src/shipping/components/ShippingZoneRatesPage/ShippingZoneRatesPage.tsx @@ -16,7 +16,7 @@ import { ShippingMethodTypeEnum, ShippingMethodTypeFragment, ShippingZoneQuery, - TaxClassFragment, + TaxClassBaseFragment, } from "@saleor/graphql"; import useForm, { SubmitPromise } from "@saleor/hooks/useForm"; import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit"; @@ -70,7 +70,7 @@ export interface ShippingZoneRatesPageProps onProductAssign: () => void; onProductUnassign: (ids: string[]) => void; variant: ShippingMethodTypeEnum; - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; fetchMoreTaxClasses: FetchMoreProps; } diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 43ca4c550..c66c4ab5c 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -251291,6 +251291,172 @@ exports[`Storyshots Views / Taxes / Tax classes view default 1`] = ` +
+
+ + +
+
+ +
@@ -251599,6 +251765,136 @@ exports[`Storyshots Views / Taxes / Tax classes view loading 1`] = ` />
+
+
+ +
+ + ‌ + +
+
+
+
+ +
+ + ‌ + +
+
diff --git a/src/taxes/fixtures.ts b/src/taxes/fixtures.ts index 4ad99947e..6fa3c3bed 100644 --- a/src/taxes/fixtures.ts +++ b/src/taxes/fixtures.ts @@ -166,6 +166,8 @@ export const taxClasses: TaxClassFragment[] = [ rate: 0.15, }, ], + metadata: [], + privateMetadata: [], }, { __typename: "TaxClass", @@ -191,5 +193,7 @@ export const taxClasses: TaxClassFragment[] = [ rate: 0.0, }, ], + metadata: [], + privateMetadata: [], }, ]; diff --git a/src/taxes/mutations.ts b/src/taxes/mutations.ts index 174987262..37c7c9a29 100644 --- a/src/taxes/mutations.ts +++ b/src/taxes/mutations.ts @@ -7,7 +7,7 @@ export const taxConfigurationUpdate = gql` ) { taxConfigurationUpdate(id: $id, input: $input) { errors { - ...TaxConfigurationUpdateErrorFragment + ...TaxConfigurationUpdateError } taxConfiguration { ...TaxConfiguration @@ -26,7 +26,7 @@ export const taxCountryConfigurationUpdate = gql` updateTaxClassRates: $updateTaxClassRates ) { errors { - ...TaxCountryConfigurationUpdateErrorFragment + ...TaxCountryConfigurationUpdateError } taxCountryConfiguration { ...TaxCountryConfiguration @@ -39,7 +39,7 @@ export const taxCountryConfigurationDelete = gql` mutation TaxCountryConfigurationDelete($countryCode: CountryCode!) { taxCountryConfigurationDelete(countryCode: $countryCode) { errors { - ...TaxCountryConfigurationDeleteErrorFragment + ...TaxCountryConfigurationDeleteError } taxCountryConfiguration { ...TaxCountryConfiguration @@ -52,7 +52,7 @@ export const taxClassUpdate = gql` mutation TaxClassUpdate($id: ID!, $input: TaxClassUpdateInput!) { taxClassUpdate(id: $id, input: $input) { errors { - ...TaxClassUpdateErrorFragment + ...TaxClassUpdateError } taxClass { ...TaxClass @@ -65,7 +65,7 @@ export const taxClassCreate = gql` mutation TaxClassCreate($input: TaxClassCreateInput!) { taxClassCreate(input: $input) { errors { - ...TaxClassCreateErrorFragment + ...TaxClassCreateError } taxClass { ...TaxClass @@ -78,7 +78,7 @@ export const taxClassDelete = gql` mutation TaxClassDelete($id: ID!) { taxClassDelete(id: $id) { errors { - ...TaxClassDeleteErrorFragment + ...TaxClassDeleteError } } } diff --git a/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx b/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx index b414f23d1..c06b84de1 100644 --- a/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx +++ b/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx @@ -9,15 +9,12 @@ import VerticalSpacer from "@saleor/apps/components/VerticalSpacer"; import CardTitle from "@saleor/components/CardTitle"; import Container from "@saleor/components/Container"; import Grid from "@saleor/components/Grid"; +import Metadata from "@saleor/components/Metadata"; import PageHeader from "@saleor/components/PageHeader"; import Savebar from "@saleor/components/Savebar"; import Skeleton from "@saleor/components/Skeleton"; import { configurationMenuUrl } from "@saleor/configuration"; -import { - TaxClassCreateInput, - TaxClassFragment, - TaxClassUpdateInput, -} from "@saleor/graphql"; +import { TaxClassFragment } from "@saleor/graphql"; import { SubmitPromise } from "@saleor/hooks/useForm"; import useNavigator from "@saleor/hooks/useNavigator"; import { sectionNames } from "@saleor/intl"; @@ -34,8 +31,11 @@ import { import { parseQuery } from "@saleor/orders/components/OrderCustomerAddressesEditDialog/utils"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import { taxesMessages } from "@saleor/taxes/messages"; +import { TaxClassesPageFormData } from "@saleor/taxes/types"; import { useAutofocus } from "@saleor/taxes/utils/useAutofocus"; import { isLastElement } from "@saleor/taxes/utils/utils"; +import { getFormErrors } from "@saleor/utils/errors"; +import getTaxesErrorMessage from "@saleor/utils/errors/taxes"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -52,8 +52,8 @@ interface TaxClassesPageProps { disabled: boolean; onCreateNewButtonClick: () => void; onTaxClassDelete: (id: string) => SubmitPromise; - onTaxClassCreate: (input: TaxClassCreateInput) => SubmitPromise; - onTaxClassUpdate: (id: string, input: TaxClassUpdateInput) => SubmitPromise; + onTaxClassCreate: (data: TaxClassesPageFormData) => SubmitPromise; + onTaxClassUpdate: (data: TaxClassesPageFormData) => SubmitPromise; } export const TaxClassesPage: React.FC = props => { @@ -90,11 +90,13 @@ export const TaxClassesPage: React.FC = props => { onTaxClassUpdate={onTaxClassUpdate} disabled={disabled} > - {({ data, handlers, submit, change }) => { + {({ data, validationErrors, handlers, submit, change }) => { const filteredRates = data.updateTaxClassRates.filter( rate => rate.label.search(new RegExp(parseQuery(query), "i")) >= 0, ); + const formErrors = getFormErrors(["name"], validationErrors); + return ( @@ -140,6 +142,8 @@ export const TaxClassesPage: React.FC = props => { fullWidth inputProps={{ className: classes.namePadding }} inputRef={nameInputRef} + error={!!formErrors.name} + helperText={getTaxesErrorMessage(formErrors.name, intl)} /> @@ -238,6 +242,8 @@ export const TaxClassesPage: React.FC = props => { )} + + )} diff --git a/src/taxes/pages/TaxClassesPage/form.tsx b/src/taxes/pages/TaxClassesPage/form.tsx index 8c4cce60b..017f8ec66 100644 --- a/src/taxes/pages/TaxClassesPage/form.tsx +++ b/src/taxes/pages/TaxClassesPage/form.tsx @@ -1,56 +1,58 @@ import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog"; -import { - CountryCode, - TaxClassCreateInput, - TaxClassFragment, - TaxClassRateInput, - TaxClassUpdateInput, -} from "@saleor/graphql"; +import { TaxClassFragment } from "@saleor/graphql"; import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm"; -import useFormset, { FormsetData } from "@saleor/hooks/useFormset"; +import useFormset from "@saleor/hooks/useFormset"; import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit"; -import React from "react"; +import { TaxClassesPageFormData } from "@saleor/taxes/types"; +import { getTaxClassInitialFormData } from "@saleor/taxes/utils/data"; +import { validateTaxClassFormData } from "@saleor/taxes/utils/validation"; +import { TaxClassError } from "@saleor/utils/errors/taxes"; +import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; +import React, { useState } from "react"; -export interface TaxClassesPageFormData { - updateTaxClassRates: FormsetData; - name?: string; +interface TaxClassesFormHandlers { + handleRateChange: (id: string, value: string) => void; + changeMetadata: FormChange; } + export interface UseTaxClassesFormResult { + validationErrors: TaxClassError[]; data: TaxClassesPageFormData; - submit: () => SubmitPromise; + submit: () => SubmitPromise; change: FormChange; - handlers: { handleRateChange: (id: string, value: string) => void }; + handlers: TaxClassesFormHandlers; } interface TaxClassesFormProps { - children: (props: any) => React.ReactNode; + children: (props: UseTaxClassesFormResult) => React.ReactNode; taxClass: TaxClassFragment | undefined; - onTaxClassCreate: (data: TaxClassCreateInput) => SubmitPromise; - onTaxClassUpdate: (id: string, data: TaxClassUpdateInput) => SubmitPromise; + onTaxClassCreate: ( + data: TaxClassesPageFormData, + ) => SubmitPromise; + onTaxClassUpdate: ( + data: TaxClassesPageFormData, + ) => SubmitPromise; disabled: boolean; } function useTaxClassesForm( taxClass: TaxClassFragment, - onTaxClassCreate, - onTaxClassUpdate, - disabled, + onTaxClassCreate: ( + data: TaxClassesPageFormData, + ) => SubmitPromise, + onTaxClassUpdate: ( + data: TaxClassesPageFormData, + ) => SubmitPromise, + disabled: boolean, ): UseTaxClassesFormResult { // Initial - const initialFormData: TaxClassUpdateInput = { - name: taxClass?.name ?? "", - }; const isNewTaxClass = taxClass?.id === "new"; - const initialFormsetData = taxClass?.countries - .map(item => ({ - id: item.country.code, - label: item.country.country, - value: item.rate?.toString() ?? "", - data: null, - })) - .sort((a, b) => a.label.localeCompare(b.label)); + const initialFormData = getTaxClassInitialFormData(taxClass); + const initialFormsetData = initialFormData.updateTaxClassRates; + + const formset = useFormset(initialFormsetData); const { formId, triggerChange, data, handleChange } = useForm( initialFormData, @@ -60,42 +62,41 @@ function useTaxClassesForm( }, ); + const [validationErrors, setValidationErrors] = useState([]); + if (isNewTaxClass) { triggerChange(); } - const formset = useFormset(initialFormsetData); - // Handlers + const handleRateChange = (id: string, value: string) => { triggerChange(); formset.change(id, value); }; + const { + makeChangeHandler: makeMetadataChangeHandler, + } = useMetadataChangeTrigger(); + + const changeMetadata = makeMetadataChangeHandler(handleChange); + // Submit - const submitData = { - name: data.name, - [isNewTaxClass - ? "createCountryRates" - : "updateCountryRates"]: formset.data.flatMap(item => { - const { id, value } = item; - if (!value && isNewTaxClass) { - return []; - } - const parsedRate = parseFloat(value); - return { - rate: parsedRate, - countryCode: id as CountryCode, - }; - }), - }; - const handleSubmit = async (data: TaxClassUpdateInput) => { - const errors = isNewTaxClass - ? await onTaxClassCreate(data) - : await onTaxClassUpdate(taxClass.id, data); + const handleSubmit = async (data: TaxClassesPageFormData) => { + const errors = validateTaxClassFormData(data); - return errors; + setValidationErrors(errors); + + if (errors.length) { + return errors; + } + + if (isNewTaxClass) { + return onTaxClassCreate(data); + } + + return onTaxClassUpdate(data); }; const handleFormSubmit = useHandleFormSubmit({ @@ -103,7 +104,11 @@ function useTaxClassesForm( onSubmit: handleSubmit, }); - const submit = () => handleFormSubmit(submitData); + const submit = () => + handleFormSubmit({ + ...data, + updateTaxClassRates: formset.data, + }); // Exit form util @@ -118,8 +123,9 @@ function useTaxClassesForm( setIsSubmitDisabled(disabled); return { + validationErrors, data: { ...data, updateTaxClassRates: formset.data }, - handlers: { handleRateChange }, + handlers: { handleRateChange, changeMetadata }, change: handleChange, submit, }; diff --git a/src/taxes/types.ts b/src/taxes/types.ts new file mode 100644 index 000000000..7e91a023d --- /dev/null +++ b/src/taxes/types.ts @@ -0,0 +1,9 @@ +import { MetadataFormData } from "@saleor/components/Metadata"; +import { TaxClassRateInput } from "@saleor/graphql"; +import { FormsetData } from "@saleor/hooks/useFormset"; + +export interface TaxClassesPageFormData extends MetadataFormData { + id: string; + updateTaxClassRates: FormsetData; + name?: string; +} diff --git a/src/taxes/utils/data.ts b/src/taxes/utils/data.ts new file mode 100644 index 000000000..ead7112cf --- /dev/null +++ b/src/taxes/utils/data.ts @@ -0,0 +1,59 @@ +import { + CountryCode, + CountryRateInput, + TaxClassCreateInput, + TaxClassFragment, + TaxClassUpdateInput, +} from "@saleor/graphql"; +import { FormsetAtomicData } from "@saleor/hooks/useFormset"; +import { mapMetadataItemToInput } from "@saleor/utils/maps"; + +import { TaxClassesPageFormData } from "../types"; + +export const getTaxClassInitialFormData = ( + taxClass?: TaxClassFragment, +): TaxClassesPageFormData => { + const initialCountries = taxClass?.countries + .map(item => ({ + id: item.country.code, + label: item.country.country, + value: item.rate?.toString() ?? "", + data: null, + })) + .sort((a, b) => a.label.localeCompare(b.label)); + + return { + id: taxClass?.id, + name: taxClass?.name ?? "", + metadata: taxClass?.metadata?.map(mapMetadataItemToInput), + privateMetadata: taxClass?.privateMetadata?.map(mapMetadataItemToInput), + updateTaxClassRates: initialCountries, + }; +}; + +const createCountryRateInput = ({ + id, + value, +}: FormsetAtomicData): CountryRateInput => ({ + countryCode: id as CountryCode, + rate: parseFloat(value), +}); + +export const createTaxClassCreateInput = ( + data: TaxClassesPageFormData, +): TaxClassCreateInput => ({ + name: data.name, + createCountryRates: data.updateTaxClassRates.flatMap(item => { + if (!item.value) { + return []; + } + return createCountryRateInput(item); + }), +}); + +export const createTaxClassUpdateInput = ( + data: TaxClassesPageFormData, +): TaxClassUpdateInput => ({ + name: data.name, + updateCountryRates: data.updateTaxClassRates.flatMap(createCountryRateInput), +}); diff --git a/src/taxes/utils/useTaxClassFetchMore.ts b/src/taxes/utils/useTaxClassFetchMore.ts index f38f8cf1f..8032bcf3c 100644 --- a/src/taxes/utils/useTaxClassFetchMore.ts +++ b/src/taxes/utils/useTaxClassFetchMore.ts @@ -1,9 +1,9 @@ -import { TaxClassFragment, useTaxClassAssignQuery } from "@saleor/graphql"; +import { TaxClassBaseFragment, useTaxClassAssignQuery } from "@saleor/graphql"; import { FetchMoreProps } from "@saleor/types"; import { mapEdgesToItems } from "@saleor/utils/maps"; interface UseTaxClassFetchMoreHookResult { - taxClasses: Array>; + taxClasses: TaxClassBaseFragment[]; fetchMoreTaxClasses: FetchMoreProps; } diff --git a/src/taxes/utils/validation.ts b/src/taxes/utils/validation.ts new file mode 100644 index 000000000..771f7d751 --- /dev/null +++ b/src/taxes/utils/validation.ts @@ -0,0 +1,21 @@ +import { CommonError, CommonErrorCode } from "@saleor/utils/errors/common"; + +import { TaxClassesPageFormData } from "../types"; + +export const createEmptyRequiredError = ( + field: string, +): CommonError => ({ + code: CommonErrorCode.REQUIRED, + field, + message: null, +}); + +export const validateTaxClassFormData = (data: TaxClassesPageFormData) => { + let errors: Array> = []; + + if (!data.name) { + errors = [...errors, createEmptyRequiredError("name")]; + } + + return errors; +}; diff --git a/src/taxes/views/TaxClassesList.tsx b/src/taxes/views/TaxClassesList.tsx index 91a93c331..7658231b4 100644 --- a/src/taxes/views/TaxClassesList.tsx +++ b/src/taxes/views/TaxClassesList.tsx @@ -1,23 +1,33 @@ import { - TaxClassCreateInput, + TaxClassCreateErrorFragment, TaxClassFragment, - TaxClassUpdateInput, useTaxClassCreateMutation, useTaxClassDeleteMutation, useTaxClassesListQuery, useTaxClassUpdateMutation, useTaxCountriesListQuery, + useUpdateMetadataMutation, + useUpdatePrivateMetadataMutation, } from "@saleor/graphql"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; +import createMetadataCreateHandler, { + CreateMetadataHandlerFunctionResult, +} from "@saleor/utils/handlers/metadataCreateHandler"; +import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler"; import { mapEdgesToItems } from "@saleor/utils/maps"; import React from "react"; import { useIntl } from "react-intl"; import { taxesMessages } from "../messages"; import TaxClassesPage from "../pages/TaxClassesPage"; +import { TaxClassesPageFormData } from "../types"; import { taxClassesListUrl, TaxTab, taxTabPath } from "../urls"; +import { + createTaxClassCreateInput, + createTaxClassUpdateInput, +} from "../utils/data"; import { useTaxUrlRedirect } from "../utils/useTaxUrlRedirect"; import { mapUndefinedCountriesToTaxClasses } from "../utils/utils"; @@ -40,12 +50,17 @@ export const TaxClassesList: React.FC = ({ id }) => { id: "new", name: intl.formatMessage(taxesMessages.newTaxClass), countries: [], + metadata: [], + privateMetadata: [], }), [intl], ); const isNewTaxClass = id === "new"; + const [updateMetadata] = useUpdateMetadataMutation({}); + const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({}); + const [taxClassDeleteMutation] = useTaxClassDeleteMutation({ onCompleted: data => { const errors = data?.taxClassDelete?.errors; @@ -89,15 +104,23 @@ export const TaxClassesList: React.FC = ({ id }) => { }, }); - const handleCreateTaxClass = async (input: TaxClassCreateInput) => { + const createTaxClass = async ( + data: TaxClassesPageFormData, + ): Promise> => { const res = await taxClassCreateMutation({ variables: { - input, + input: createTaxClassCreateInput(data), }, }); - refetch(); - navigate(res?.data?.taxClassCreate?.taxClass?.id); - return res; + + const taxClassCreate = res?.data?.taxClassCreate; + + return { + id: taxClassCreate?.taxClass?.id, + errors: taxClassCreate?.errors, + }; }; const handleDeleteTaxClass = async (id: string) => { @@ -114,14 +137,17 @@ export const TaxClassesList: React.FC = ({ id }) => { } }; - const handleUpdateTaxClass = async (id: string, input: TaxClassUpdateInput) => - taxClassUpdateMutation({ + const updateTaxClass = async (data: TaxClassesPageFormData) => { + const res = await taxClassUpdateMutation({ variables: { - id, - input, + id: data.id, + input: createTaxClassUpdateInput(data), }, }); + return res?.data?.taxClassUpdate?.errors || []; + }; + const { data, refetch } = useTaxClassesListQuery({ variables: { first: 100 }, }); @@ -152,10 +178,34 @@ export const TaxClassesList: React.FC = ({ id }) => { newTaxClass, ]); - const savebarState = - id === "new" - ? taxClassCreateMutationState.status - : taxClassUpdateMutationState.status; + const selectedTaxClass = React.useMemo(() => { + if (isNewTaxClass) { + return newTaxClass; + } + + return taxClasses?.find(taxClass => taxClass.id === id); + }, [id, isNewTaxClass, newTaxClass, taxClasses]); + + const handleCreateTaxClass = createMetadataCreateHandler( + createTaxClass, + updateMetadata, + updatePrivateMetadata, + id => { + refetch(); + navigate(id); + }, + ); + + const handleUpdateTaxClass = createMetadataUpdateHandler( + selectedTaxClass, + updateTaxClass, + variables => updateMetadata({ variables }), + variables => updatePrivateMetadata({ variables }), + ); + + const savebarState = isNewTaxClass + ? taxClassCreateMutationState.status + : taxClassUpdateMutationState.status; useTaxUrlRedirect({ id, diff --git a/src/utils/errors/account.ts b/src/utils/errors/account.ts index c09a705a0..f88a1d33d 100644 --- a/src/utils/errors/account.ts +++ b/src/utils/errors/account.ts @@ -49,6 +49,7 @@ const messages = defineMessages({ interface ErrorFragment { code: AccountErrorCode | SetPasswordData["errors"][number]["code"]; + field: string | null; } function getAccountErrorMessage(err: ErrorFragment, intl: IntlShape): string { diff --git a/src/utils/errors/common.ts b/src/utils/errors/common.ts index 3e831281f..cee12d14e 100644 --- a/src/utils/errors/common.ts +++ b/src/utils/errors/common.ts @@ -16,11 +16,18 @@ const commonErrorMessages = defineMessages({ }, }); -type CommonErrorCode = "GRAPHQL_ERROR" | "INVALID" | "REQUIRED"; +export const CommonErrorCode = { + GRAPHQL_ERROR: "GRAPHQL_ERROR", + INVALID: "INVALID", + REQUIRED: "REQUIRED", +} as const; -interface CommonError { +export type CommonErrorCode = typeof CommonErrorCode[keyof typeof CommonErrorCode]; + +export interface CommonError { code: ErrorCode | CommonErrorCode; - field?: string | null; + field: string | null; + message?: string | null; } export function getCommonFormFieldErrorMessage( diff --git a/src/utils/errors/taxes.ts b/src/utils/errors/taxes.ts new file mode 100644 index 000000000..e404c7b91 --- /dev/null +++ b/src/utils/errors/taxes.ts @@ -0,0 +1,27 @@ +import { + TaxClassCreateErrorFragment, + TaxClassDeleteErrorFragment, + TaxClassUpdateErrorFragment, +} from "@saleor/graphql"; +import { IntlShape } from "react-intl"; + +import { + CommonError, + CommonErrorCode, + getCommonFormFieldErrorMessage, +} from "./common"; + +export type TaxClassError = + | TaxClassUpdateErrorFragment + | TaxClassCreateErrorFragment + | TaxClassDeleteErrorFragment + | CommonError; + +function getTaxesErrorMessage( + err: Omit | undefined, + intl: IntlShape, +): string { + return getCommonFormFieldErrorMessage(err, intl); +} + +export default getTaxesErrorMessage; diff --git a/src/utils/handlers/metadataCreateHandler.ts b/src/utils/handlers/metadataCreateHandler.ts index 93f1ef694..a9b4f4817 100644 --- a/src/utils/handlers/metadataCreateHandler.ts +++ b/src/utils/handlers/metadataCreateHandler.ts @@ -15,6 +15,7 @@ function createMetadataCreateHandler( create: (data: T) => Promise>, setMetadata: UpdateMetadataMutationFn, setPrivateMetadata: UpdatePrivateMetadataMutationFn, + onComplete?: (id: string) => void, ) { return async (data: T) => { const { id, errors } = await create(data); @@ -60,6 +61,10 @@ function createMetadataCreateHandler( } } + if (onComplete) { + onComplete(id); + } + return []; }; }