Handle attribute errors in product views

This commit is contained in:
dominik-zeglen 2020-09-29 17:04:48 +02:00
parent 369fd09853
commit ee1ecff1f8
6 changed files with 51 additions and 26 deletions

View file

@ -8,10 +8,10 @@ import SingleAutocompleteSelectField, {
SingleAutocompleteChoiceType SingleAutocompleteChoiceType
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { ProductVariant_attributes_attribute_values } from "@saleor/fragments/types/ProductVariant"; import { ProductVariant_attributes_attribute_values } from "@saleor/fragments/types/ProductVariant";
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { VariantCreate_productVariantCreate_errors } from "@saleor/products/types/VariantCreate";
import { getProductVariantAttributeErrorMessage } from "@saleor/utils/errors/product"; import { getProductVariantAttributeErrorMessage } from "@saleor/utils/errors/product";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -27,7 +27,7 @@ export type VariantAttributeInput = FormsetAtomicData<
interface ProductVariantAttributesProps { interface ProductVariantAttributesProps {
attributes: VariantAttributeInput[]; attributes: VariantAttributeInput[];
disabled: boolean; disabled: boolean;
errors: VariantCreate_productVariantCreate_errors[]; errors: ProductErrorFragment[];
onChange: FormsetChange<VariantAttributeInputData>; onChange: FormsetChange<VariantAttributeInputData>;
} }
@ -84,31 +84,46 @@ const ProductVariantAttributes: React.FC<ProductVariantAttributesProps> = ({
{attributes === undefined ? ( {attributes === undefined ? (
<Skeleton /> <Skeleton />
) : ( ) : (
attributes.map(attribute => ( attributes.map(attribute => {
<SingleAutocompleteSelectField const error = errors.find(
key={attribute.id} err => err.attributeId === attribute.id
disabled={disabled} );
displayValue={getAttributeDisplayValue(
attribute.id, return (
attribute.value, <SingleAutocompleteSelectField
attributes key={attribute.id}
)} disabled={disabled}
label={attribute.label} displayValue={getAttributeDisplayValue(
name={`attribute:${attribute.id}`} attribute.id,
onChange={event => onChange(attribute.id, event.target.value)} attribute.value,
value={getAttributeValue(attribute.id, attributes)} attributes
choices={getAttributeValueChoices(attribute.id, attributes)} )}
allowCustomValues emptyOption
data-test="variant-attribute-input" error={!!error}
/> helperText={getProductVariantAttributeErrorMessage(
)) error,
intl
)}
label={attribute.label}
name={`attribute:${attribute.id}`}
onChange={event => onChange(attribute.id, event.target.value)}
value={getAttributeValue(attribute.id, attributes)}
choices={getAttributeValueChoices(attribute.id, attributes)}
allowCustomValues
data-test="variant-attribute-input"
/>
);
})
)} )}
</Grid> </Grid>
{errors.length > 0 && ( {errors.length > 0 && (
<> <>
<FormSpacer /> <FormSpacer />
{errors {errors
.filter(error => error.field === "attributes") .filter(
error =>
error.field === "attributes" && error.attributeId === null
)
.map(error => ( .map(error => (
<Typography color="error" key={error.code}> <Typography color="error" key={error.code}>
{getProductVariantAttributeErrorMessage(error, intl)} {getProductVariantAttributeErrorMessage(error, intl)}

View file

@ -28,6 +28,9 @@ const useStyles = makeStyles(
colName: { colName: {
paddingLeft: 0 paddingLeft: 0
}, },
firstVariant: {
width: 88
},
link: { link: {
cursor: "pointer" cursor: "pointer"
}, },
@ -126,7 +129,10 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
className={classNames( className={classNames(
classes.colAvatar, classes.colAvatar,
classes.tabActive, classes.tabActive,
classes.noHandle classes.noHandle,
{
[classes.firstVariant]: variants.length === 0
}
)} )}
thumbnail={null} thumbnail={null}
colSpan={2} colSpan={2}

View file

@ -8,13 +8,13 @@ import { MetadataFormData } from "@saleor/components/Metadata";
import Metadata from "@saleor/components/Metadata/Metadata"; import Metadata from "@saleor/components/Metadata/Metadata";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { ProductVariant } from "@saleor/fragments/types/ProductVariant"; import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
import { WarehouseFragment } from "@saleor/fragments/types/WarehouseFragment"; import { WarehouseFragment } from "@saleor/fragments/types/WarehouseFragment";
import useFormset, { import useFormset, {
FormsetChange, FormsetChange,
FormsetData FormsetData
} from "@saleor/hooks/useFormset"; } from "@saleor/hooks/useFormset";
import { VariantUpdate_productVariantUpdate_errors } from "@saleor/products/types/VariantUpdate";
import { import {
getAttributeInputFromVariant, getAttributeInputFromVariant,
getStockInputFromVariant getStockInputFromVariant
@ -56,7 +56,7 @@ export interface ProductVariantPageSubmitData
interface ProductVariantPageProps { interface ProductVariantPageProps {
defaultWeightUnit: string; defaultWeightUnit: string;
variant?: ProductVariant; variant?: ProductVariant;
errors: VariantUpdate_productVariantUpdate_errors[]; errors: ProductErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
loading?: boolean; loading?: boolean;
placeholderImage?: string; placeholderImage?: string;

View file

@ -36,14 +36,17 @@ storiesOf("Views / Products / Create product variant", module)
disabled={false} disabled={false}
errors={[ errors={[
{ {
attributeId: product.productType.variantAttributes[0].id,
code: ProductErrorCode.REQUIRED, code: ProductErrorCode.REQUIRED,
field: "attributes" field: "attributes"
}, },
{ {
attributeId: null,
code: ProductErrorCode.UNIQUE, code: ProductErrorCode.UNIQUE,
field: "attributes" field: "attributes"
}, },
{ {
attributeId: null,
code: ProductErrorCode.ALREADY_EXISTS, code: ProductErrorCode.ALREADY_EXISTS,
field: "sku" field: "sku"
} }

View file

@ -86,14 +86,17 @@ storiesOf("Views / Products / Product variant details", module)
saveButtonBarState="default" saveButtonBarState="default"
errors={[ errors={[
{ {
attributeId: variant.attributes[0].attribute.id,
code: ProductErrorCode.REQUIRED, code: ProductErrorCode.REQUIRED,
field: "attributes" field: "attributes"
}, },
{ {
attributeId: null,
code: ProductErrorCode.UNIQUE, code: ProductErrorCode.UNIQUE,
field: "attributes" field: "attributes"
}, },
{ {
attributeId: null,
code: ProductErrorCode.ALREADY_EXISTS, code: ProductErrorCode.ALREADY_EXISTS,
field: "sku" field: "sku"
} }

View file

@ -83,8 +83,6 @@ export function getProductVariantAttributeErrorMessage(
): string { ): string {
if (err) { if (err) {
switch (err.code) { switch (err.code) {
case ProductErrorCode.REQUIRED:
return intl.formatMessage(messages.attributeRequired);
case ProductErrorCode.UNIQUE: case ProductErrorCode.UNIQUE:
return intl.formatMessage(messages.variantUnique); return intl.formatMessage(messages.variantUnique);
default: default: