Merge pull request #737 from mirumee/fix/handle-attribute-errors

Fix handle attribute errors
This commit is contained in:
Dominik Żegleń 2020-10-13 15:14:26 +02:00 committed by GitHub
commit fca8d7ab28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 584 additions and 150 deletions

View file

@ -688,6 +688,7 @@ type BulkProductError {
field: String field: String
message: String message: String
code: ProductErrorCode! code: ProductErrorCode!
attributes: [ID!]
index: Int index: Int
warehouses: [ID!] warehouses: [ID!]
} }
@ -696,6 +697,7 @@ type BulkStockError {
field: String field: String
message: String message: String
code: ProductErrorCode! code: ProductErrorCode!
attributes: [ID!]
index: Int index: Int
} }
@ -3744,6 +3746,7 @@ type ProductError {
field: String field: String
message: String message: String
code: ProductErrorCode! code: ProductErrorCode!
attributes: [ID!]
} }
enum ProductErrorCode { enum ProductErrorCode {
@ -3755,6 +3758,7 @@ enum ProductErrorCode {
GRAPHQL_ERROR GRAPHQL_ERROR
INVALID INVALID
NOT_PRODUCTS_IMAGE NOT_PRODUCTS_IMAGE
NOT_PRODUCTS_VARIANT
NOT_FOUND NOT_FOUND
REQUIRED REQUIRED
UNIQUE UNIQUE

View file

@ -4,7 +4,7 @@ import TextField from "@material-ui/core/TextField";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { FetchMoreProps } from "@saleor/types"; import { FetchMoreProps } from "@saleor/types";
import classNames from "classnames"; import classNames from "classnames";
import Downshift from "downshift"; import Downshift, { ControllerStateAndHelpers } from "downshift";
import { filter } from "fuzzaldrin"; import { filter } from "fuzzaldrin";
import React from "react"; import React from "react";
@ -76,20 +76,27 @@ const SingleAutocompleteSelectFieldComponent: React.FC<SingleAutocompleteSelectF
const [prevDisplayValue] = useStateFromProps(displayValue); const [prevDisplayValue] = useStateFromProps(displayValue);
const handleChange = item => const handleChange = (
item: string,
stateAndHelpers: ControllerStateAndHelpers
) => {
onChange({ onChange({
target: { target: {
name, name,
value: item value: item
} }
} as any); } as any);
stateAndHelpers.reset({
inputValue: item
});
};
return ( return (
<DebounceAutocomplete debounceFn={fetchChoices}> <DebounceAutocomplete debounceFn={fetchChoices}>
{debounceFn => ( {debounceFn => (
<Downshift <Downshift
defaultInputValue={displayValue} defaultInputValue={displayValue}
itemToString={() => displayValue} itemToString={() => displayValue || ""}
onInputValueChange={value => debounceFn(value)} onInputValueChange={value => debounceFn(value)}
onSelect={handleChange} onSelect={handleChange}
selectedItem={value} selectedItem={value}
@ -190,6 +197,7 @@ const SingleAutocompleteSelectField: React.FC<SingleAutocompleteSelectFieldProps
...rest ...rest
}) => { }) => {
const [query, setQuery] = React.useState(""); const [query, setQuery] = React.useState("");
if (fetchChoices) { if (fetchChoices) {
return ( return (
<DebounceAutocomplete debounceFn={fetchChoices}> <DebounceAutocomplete debounceFn={fetchChoices}>

View file

@ -35,7 +35,7 @@ export interface SingleAutocompleteSelectFieldContentProps
choices: SingleAutocompleteChoiceType[]; choices: SingleAutocompleteChoiceType[];
displayCustomValue: boolean; displayCustomValue: boolean;
emptyOption: boolean; emptyOption: boolean;
getItemProps: (options: GetItemPropsOptions) => void; getItemProps: (options: GetItemPropsOptions) => any;
highlightedIndex: number; highlightedIndex: number;
inputValue: string; inputValue: string;
isCustomValueSelected: boolean; isCustomValueSelected: boolean;
@ -164,9 +164,11 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
React.useEffect(() => { React.useEffect(() => {
setSlice(sliceSize); setSlice(sliceSize);
anchor.current.scrollTo({ if (anchor.current?.scrollTo) {
top: 0 anchor.current.scrollTo({
}); top: 0
});
}
}, [choices?.length]); }, [choices?.length]);
React.useEffect(() => { React.useEffect(() => {
@ -175,6 +177,10 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
} }
}, [loading]); }, [loading]);
const emptyOptionProps = getItemProps({
item: ""
});
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<div <div
@ -188,10 +194,9 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
<MenuItem <MenuItem
className={classes.menuItem} className={classes.menuItem}
component="div" component="div"
{...getItemProps({
item: ""
})}
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="empty"
{...emptyOptionProps}
> >
<Typography color="textSecondary"> <Typography color="textSecondary">
<FormattedMessage defaultMessage="None" /> <FormattedMessage defaultMessage="None" />
@ -206,6 +211,7 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
item: inputValue item: inputValue
})} })}
data-test="singleautocomplete-select-option-add" data-test="singleautocomplete-select-option-add"
data-test-type="add"
onClick={add.onClick} onClick={add.onClick}
> >
<Add color="primary" className={classes.add} /> <Add color="primary" className={classes.add} />
@ -222,6 +228,7 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
item: inputValue item: inputValue
})} })}
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="custom"
> >
<FormattedMessage <FormattedMessage
defaultMessage="Add new value: {value}" defaultMessage="Add new value: {value}"
@ -254,6 +261,8 @@ const SingleAutocompleteSelectFieldContent: React.FC<SingleAutocompleteSelectFie
item: suggestion.value item: suggestion.value
})} })}
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-value={suggestion.value}
data-test-type="option"
> >
{suggestion.label} {suggestion.label}
</MenuItem> </MenuItem>

View file

@ -7,6 +7,14 @@ export const productErrorFragment = gql`
} }
`; `;
export const productErrorWithAttributesFragment = gql`
${productErrorFragment}
fragment ProductErrorWithAttributesFragment on ProductError {
...ProductErrorFragment
attributes
}
`;
export const accountErrorFragment = gql` export const accountErrorFragment = gql`
fragment AccountErrorFragment on AccountError { fragment AccountErrorFragment on AccountError {
code code

View file

@ -0,0 +1,16 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL fragment: ProductErrorWithAttributesFragment
// ====================================================
export interface ProductErrorWithAttributesFragment {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
attributes: string[] | null;
}

View file

@ -13,10 +13,11 @@ import MultiAutocompleteSelectField, {
import SingleAutocompleteSelectField, { import SingleAutocompleteSelectField, {
SingleAutocompleteChoiceType SingleAutocompleteChoiceType
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
import { maybe } from "@saleor/misc";
import { ProductDetails_product_attributes_attribute_values } from "@saleor/products/types/ProductDetails"; import { ProductDetails_product_attributes_attribute_values } from "@saleor/products/types/ProductDetails";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { getProductErrorMessage } from "@saleor/utils/errors";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
@ -33,6 +34,7 @@ export type ProductAttributeInput = FormsetAtomicData<
export interface ProductAttributesProps { export interface ProductAttributesProps {
attributes: ProductAttributeInput[]; attributes: ProductAttributeInput[];
disabled: boolean; disabled: boolean;
errors: ProductErrorWithAttributesFragment[];
onChange: FormsetChange; onChange: FormsetChange;
onMultiChange: FormsetChange; onMultiChange: FormsetChange;
} }
@ -125,6 +127,7 @@ function getSingleChoices(
const ProductAttributes: React.FC<ProductAttributesProps> = ({ const ProductAttributes: React.FC<ProductAttributesProps> = ({
attributes, attributes,
disabled, disabled,
errors,
onChange, onChange,
onMultiChange onMultiChange
}) => { }) => {
@ -169,61 +172,69 @@ const ProductAttributes: React.FC<ProductAttributesProps> = ({
{expanded && attributes.length > 0 && ( {expanded && attributes.length > 0 && (
<> <>
<Hr /> <Hr />
{attributes.map((attribute, attributeIndex) => ( {attributes.map((attribute, attributeIndex) => {
<React.Fragment key={attribute.id}> const error = errors.find(err =>
{attributeIndex > 0 && <Hr />} err.attributes?.includes(attribute.id)
<Grid className={classes.attributeSection} variant="uniform"> );
<div
className={classes.attributeSectionLabel} return (
data-test="product-attribute-label" <React.Fragment key={attribute.id}>
> {attributeIndex > 0 && <Hr />}
<Typography>{attribute.label}</Typography> <Grid className={classes.attributeSection} variant="uniform">
</div> <div
<div data-test="product-attribute-value"> className={classes.attributeSectionLabel}
{attribute.data.inputType === data-test="product-attribute-label"
AttributeInputTypeEnum.DROPDOWN ? ( >
<SingleAutocompleteSelectField <Typography>{attribute.label}</Typography>
choices={getSingleChoices(attribute.data.values)} </div>
disabled={disabled} <div data-test="product-attribute-value">
displayValue={maybe( {attribute.data.inputType ===
() => AttributeInputTypeEnum.DROPDOWN ? (
<SingleAutocompleteSelectField
choices={getSingleChoices(attribute.data.values)}
disabled={disabled}
displayValue={
attribute.data.values.find( attribute.data.values.find(
value => value.slug === attribute.value[0] value => value.slug === attribute.value[0]
).name, )?.name || ""
attribute.value[0] }
)} emptyOption={!attribute.data.isRequired}
emptyOption error={!!error}
name={`attribute:${attribute.label}`} helperText={getProductErrorMessage(error, intl)}
label={intl.formatMessage({ name={`attribute:${attribute.label}`}
defaultMessage: "Value", label={intl.formatMessage({
description: "attribute value" defaultMessage: "Value",
})} description: "attribute value"
value={attribute.value[0]} })}
onChange={event => value={attribute.value[0]}
onChange(attribute.id, event.target.value) onChange={event =>
} onChange(attribute.id, event.target.value)
allowCustomValues={!attribute.data.isRequired} }
/> allowCustomValues={!attribute.data.isRequired}
) : ( />
<MultiAutocompleteSelectField ) : (
choices={getMultiChoices(attribute.data.values)} <MultiAutocompleteSelectField
displayValues={getMultiDisplayValue(attribute)} choices={getMultiChoices(attribute.data.values)}
label={intl.formatMessage({ displayValues={getMultiDisplayValue(attribute)}
defaultMessage: "Values", error={!!error}
description: "attribute values" helperText={getProductErrorMessage(error, intl)}
})} label={intl.formatMessage({
name={`attribute:${attribute.label}`} defaultMessage: "Values",
value={attribute.value} description: "attribute values"
onChange={event => })}
onMultiChange(attribute.id, event.target.value) name={`attribute:${attribute.label}`}
} value={attribute.value}
allowCustomValues={!attribute.data.isRequired} onChange={event =>
/> onMultiChange(attribute.id, event.target.value)
)} }
</div> allowCustomValues={!attribute.data.isRequired}
</Grid> />
</React.Fragment> )}
))} </div>
</Grid>
</React.Fragment>
);
})}
</> </>
)} )}
</CardContent> </CardContent>

View file

@ -10,13 +10,14 @@ import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocomplet
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 SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { TaxTypeFragment } from "@saleor/fragments/types/TaxTypeFragment"; import { TaxTypeFragment } from "@saleor/fragments/types/TaxTypeFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import useFormset from "@saleor/hooks/useFormset"; import useFormset from "@saleor/hooks/useFormset";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { import {
getAttributeInputFromProductType,
getChoices, getChoices,
ProductAttributeValueChoices, ProductAttributeValueChoices,
ProductType ProductType
@ -79,7 +80,7 @@ export interface ProductCreatePageSubmitData extends FormData {
} }
interface ProductCreatePageProps { interface ProductCreatePageProps {
errors: ProductErrorFragment[]; errors: ProductErrorWithAttributesFragment[];
collections: SearchCollections_search_edges_node[]; collections: SearchCollections_search_edges_node[];
categories: SearchCategories_search_edges_node[]; categories: SearchCategories_search_edges_node[];
currency: string; currency: string;
@ -87,6 +88,7 @@ interface ProductCreatePageProps {
fetchMoreCategories: FetchMoreProps; fetchMoreCategories: FetchMoreProps;
fetchMoreCollections: FetchMoreProps; fetchMoreCollections: FetchMoreProps;
fetchMoreProductTypes: FetchMoreProps; fetchMoreProductTypes: FetchMoreProps;
initial?: Partial<FormData>;
productTypes?: Array<{ productTypes?: Array<{
id: string; id: string;
name: string; name: string;
@ -118,6 +120,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
fetchMoreCollections, fetchMoreCollections,
fetchMoreProductTypes, fetchMoreProductTypes,
header, header,
initial,
productTypes: productTypeChoiceList, productTypes: productTypeChoiceList,
saveButtonBarState, saveButtonBarState,
warehouses, warehouses,
@ -130,12 +133,21 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
}: ProductCreatePageProps) => { }: ProductCreatePageProps) => {
const intl = useIntl(); const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
const initialProductType = productTypeChoiceList?.find(
productType => initial?.productType === productType.id
);
// Form values // Form values
const { const {
change: changeAttributeData, change: changeAttributeData,
data: attributes, data: attributes,
set: setAttributeData set: setAttributeData
} = useFormset<ProductAttributeInputData>([]); } = useFormset<ProductAttributeInputData>(
initial?.productType
? getAttributeInputFromProductType(initialProductType)
: []
);
const { const {
add: addStock, add: addStock,
change: changeStockData, change: changeStockData,
@ -154,6 +166,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
} = useMetadataChangeTrigger(); } = useMetadataChangeTrigger();
const initialData: FormData = { const initialData: FormData = {
...(initial || {}),
availableForPurchase: "", availableForPurchase: "",
basePrice: 0, basePrice: 0,
category: "", category: "",
@ -185,14 +198,20 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
ProductAttributeValueChoices[] ProductAttributeValueChoices[]
>([]); >([]);
const [selectedCategory, setSelectedCategory] = useStateFromProps(""); const [selectedCategory, setSelectedCategory] = useStateFromProps(
initial?.category || ""
);
const [selectedCollections, setSelectedCollections] = useStateFromProps< const [selectedCollections, setSelectedCollections] = useStateFromProps<
MultiAutocompleteChoiceType[] MultiAutocompleteChoiceType[]
>([]); >([]);
const [productType, setProductType] = React.useState<ProductType>(null); const [productType, setProductType] = useStateFromProps<ProductType>(
const [selectedTaxType, setSelectedTaxType] = useStateFromProps(null); initialProductType || null
);
const [selectedTaxType, setSelectedTaxType] = useStateFromProps(
initial?.taxCode || null
);
const categories = getChoices(categoryChoiceList); const categories = getChoices(categoryChoiceList);
const collections = getChoices(collectionChoiceList); const collections = getChoices(collectionChoiceList);
@ -274,6 +293,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
<ProductAttributes <ProductAttributes
attributes={attributes} attributes={attributes}
disabled={disabled} disabled={disabled}
errors={errors}
onChange={handleAttributeChange} onChange={handleAttributeChange}
onMultiChange={handleAttributeMultiChange} onMultiChange={handleAttributeMultiChange}
/> />

View file

@ -0,0 +1,92 @@
import placeholderImage from "@assets/images/placeholder255x255.png";
import { collections } from "@saleor/collections/fixtures";
import { fetchMoreProps, listActionsProps } from "@saleor/fixtures";
import { product as productFixture } from "@saleor/products/fixtures";
import { taxTypes } from "@saleor/storybook/stories/taxes/fixtures";
import { warehouseList } from "@saleor/warehouses/fixtures";
import Wrapper from "@test/wrapper";
import { configure, mount } from "enzyme";
import React from "react";
import ProductUpdatePage, { ProductUpdatePageProps } from "./ProductUpdatePage";
const product = productFixture(placeholderImage);
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
const onSubmit = jest.fn();
const props: ProductUpdatePageProps = {
...listActionsProps,
categories: [product.category],
collections,
defaultWeightUnit: "kg",
disabled: false,
errors: [],
fetchCategories: () => undefined,
fetchCollections: () => undefined,
fetchMoreCategories: fetchMoreProps,
fetchMoreCollections: fetchMoreProps,
header: product.name,
images: product.images,
onBack: () => undefined,
onDelete: () => undefined,
onImageDelete: () => undefined,
onImageUpload: () => undefined,
onSetDefaultVariant: () => undefined,
onSubmit,
onVariantAdd: () => undefined,
onVariantReorder: () => undefined,
onVariantShow: () => undefined,
onVariantsAdd: () => undefined,
onWarehouseConfigure: () => undefined,
placeholderImage,
product,
saveButtonBarState: "default",
taxTypes,
variants: product.variants,
warehouses: warehouseList
};
const selectors = {
dropdown: `[data-test="autocomplete-dropdown"]`,
empty: `[data-test-type="empty"]`,
input: `[data-test="product-attribute-value"] input`
};
describe("Product details page", () => {
it("can select empty option on attribute", () => {
const component = mount(
<Wrapper>
<ProductUpdatePage {...props} />
</Wrapper>
);
expect(component.find(selectors.dropdown).exists()).toBeFalsy();
component
.find(selectors.input)
.first()
.simulate("click");
expect(component.find(selectors.dropdown).exists()).toBeTruthy();
expect(component.find(selectors.empty).exists());
component
.find(selectors.empty)
.first()
.simulate("click");
expect(
component
.find(selectors.input)
.first()
.prop("value")
).toEqual("");
component
.find("form")
.first()
.simulate("submit");
expect(onSubmit.mock.calls[0][0].attributes[0].value.length).toEqual(0);
});
});

View file

@ -9,7 +9,7 @@ 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 SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { TaxTypeFragment } from "@saleor/fragments/types/TaxTypeFragment"; import { TaxTypeFragment } from "@saleor/fragments/types/TaxTypeFragment";
import { WarehouseFragment } from "@saleor/fragments/types/WarehouseFragment"; import { WarehouseFragment } from "@saleor/fragments/types/WarehouseFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
@ -58,7 +58,7 @@ import ProductVariants from "../ProductVariants";
export interface ProductUpdatePageProps extends ListActions { export interface ProductUpdatePageProps extends ListActions {
defaultWeightUnit: string; defaultWeightUnit: string;
errors: ProductErrorFragment[]; errors: ProductErrorWithAttributesFragment[];
placeholderImage: string; placeholderImage: string;
collections: SearchCollections_search_edges_node[]; collections: SearchCollections_search_edges_node[];
categories: SearchCategories_search_edges_node[]; categories: SearchCategories_search_edges_node[];
@ -296,6 +296,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
{attributes.length > 0 && ( {attributes.length > 0 && (
<ProductAttributes <ProductAttributes
attributes={attributes} attributes={attributes}
errors={errors}
disabled={disabled} disabled={disabled}
onChange={handleAttributeChange} onChange={handleAttributeChange}
onMultiChange={handleAttributeMultiChange} onMultiChange={handleAttributeMultiChange}

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 { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
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: ProductErrorWithAttributesFragment[];
onChange: FormsetChange<VariantAttributeInputData>; onChange: FormsetChange<VariantAttributeInputData>;
} }
@ -84,31 +84,45 @@ const ProductVariantAttributes: React.FC<ProductVariantAttributesProps> = ({
{attributes === undefined ? ( {attributes === undefined ? (
<Skeleton /> <Skeleton />
) : ( ) : (
attributes.map(attribute => ( attributes.map(attribute => {
<SingleAutocompleteSelectField const error = errors.find(err =>
key={attribute.id} err.attributes?.includes(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 error={!!error}
data-test="variant-attribute-input" 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.attributes === 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

@ -7,7 +7,7 @@ import Grid from "@saleor/components/Grid";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata"; import Metadata, { MetadataFormData } from "@saleor/components/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 { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import useFormset, { import useFormset, {
FormsetChange, FormsetChange,
FormsetData FormsetData
@ -48,7 +48,7 @@ export interface ProductVariantCreatePageSubmitData
interface ProductVariantCreatePageProps { interface ProductVariantCreatePageProps {
currencySymbol: string; currencySymbol: string;
disabled: boolean; disabled: boolean;
errors: ProductErrorFragment[]; errors: ProductErrorWithAttributesFragment[];
header: string; header: string;
product: ProductVariantCreateData_product; product: ProductVariantCreateData_product;
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;

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 { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
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: ProductErrorWithAttributesFragment[];
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
loading?: boolean; loading?: boolean;
placeholderImage?: string; placeholderImage?: string;

View file

@ -24,7 +24,7 @@ export const product: (
inputType: AttributeInputTypeEnum.DROPDOWN, inputType: AttributeInputTypeEnum.DROPDOWN,
name: "Borders", name: "Borders",
slug: "Borders", slug: "Borders",
valueRequired: true, valueRequired: false,
values: [ values: [
{ {
__typename: "AttributeValue", __typename: "AttributeValue",

View file

@ -3,6 +3,7 @@ import {
bulkStockErrorFragment, bulkStockErrorFragment,
exportErrorFragment, exportErrorFragment,
productErrorFragment, productErrorFragment,
productErrorWithAttributesFragment,
stockErrorFragment stockErrorFragment
} from "@saleor/fragments/errors"; } from "@saleor/fragments/errors";
import { import {
@ -159,12 +160,12 @@ export const useProductVariantSetDefaultMutation = makeMutation<
>(productVariantSetDefault); >(productVariantSetDefault);
export const productUpdateMutation = gql` export const productUpdateMutation = gql`
${productErrorFragment} ${productErrorWithAttributesFragment}
${productFragmentDetails} ${productFragmentDetails}
mutation ProductUpdate($id: ID!, $input: ProductInput!) { mutation ProductUpdate($id: ID!, $input: ProductInput!) {
productUpdate(id: $id, input: $input) { productUpdate(id: $id, input: $input) {
errors: productErrors { errors: productErrors {
...ProductErrorFragment ...ProductErrorWithAttributesFragment
} }
product { product {
...Product ...Product
@ -179,7 +180,7 @@ export const useProductUpdateMutation = makeMutation<
export const simpleProductUpdateMutation = gql` export const simpleProductUpdateMutation = gql`
${bulkStockErrorFragment} ${bulkStockErrorFragment}
${productErrorFragment} ${productErrorWithAttributesFragment}
${productFragmentDetails} ${productFragmentDetails}
${stockErrorFragment} ${stockErrorFragment}
${fragmentVariant} ${fragmentVariant}
@ -194,7 +195,7 @@ export const simpleProductUpdateMutation = gql`
) { ) {
productUpdate(id: $id, input: $input) { productUpdate(id: $id, input: $input) {
errors: productErrors { errors: productErrors {
...ProductErrorFragment ...ProductErrorWithAttributesFragment
} }
product { product {
...Product ...Product
@ -202,7 +203,7 @@ export const simpleProductUpdateMutation = gql`
} }
productVariantUpdate(id: $productVariantId, input: $productVariantInput) { productVariantUpdate(id: $productVariantId, input: $productVariantInput) {
errors: productErrors { errors: productErrors {
...ProductErrorFragment ...ProductErrorWithAttributesFragment
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -249,12 +250,12 @@ export const useSimpleProductUpdateMutation = makeMutation<
>(simpleProductUpdateMutation); >(simpleProductUpdateMutation);
export const productCreateMutation = gql` export const productCreateMutation = gql`
${productErrorFragment} ${productErrorWithAttributesFragment}
${productFragmentDetails} ${productFragmentDetails}
mutation ProductCreate($input: ProductCreateInput!) { mutation ProductCreate($input: ProductCreateInput!) {
productCreate(input: $input) { productCreate(input: $input) {
errors: productErrors { errors: productErrors {
...ProductErrorFragment ...ProductErrorWithAttributesFragment
} }
product { product {
...Product ...Product
@ -288,7 +289,7 @@ export const useVariantDeleteMutation = makeMutation<
export const variantUpdateMutation = gql` export const variantUpdateMutation = gql`
${bulkStockErrorFragment} ${bulkStockErrorFragment}
${fragmentVariant} ${fragmentVariant}
${productErrorFragment} ${productErrorWithAttributesFragment}
mutation VariantUpdate( mutation VariantUpdate(
$addStocks: [StockInput!]! $addStocks: [StockInput!]!
$removeStocks: [ID!]! $removeStocks: [ID!]!
@ -313,7 +314,7 @@ export const variantUpdateMutation = gql`
} }
) { ) {
errors: productErrors { errors: productErrors {
...ProductErrorFragment ...ProductErrorWithAttributesFragment
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -359,11 +360,11 @@ export const useVariantUpdateMutation = makeMutation<
export const variantCreateMutation = gql` export const variantCreateMutation = gql`
${fragmentVariant} ${fragmentVariant}
${productErrorFragment} ${productErrorWithAttributesFragment}
mutation VariantCreate($input: ProductVariantCreateInput!) { mutation VariantCreate($input: ProductVariantCreateInput!) {
productVariantCreate(input: $input) { productVariantCreate(input: $input) {
errors: productErrors { errors: productErrors {
...ProductErrorFragment ...ProductErrorWithAttributesFragment
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant

View file

@ -12,6 +12,7 @@ export interface ProductCreate_productCreate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
attributes: string[] | null;
} }
export interface ProductCreate_productCreate_product_attributes_attribute_values { export interface ProductCreate_productCreate_product_attributes_attribute_values {

View file

@ -12,6 +12,7 @@ export interface ProductUpdate_productUpdate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
attributes: string[] | null;
} }
export interface ProductUpdate_productUpdate_product_attributes_attribute_values { export interface ProductUpdate_productUpdate_product_attributes_attribute_values {

View file

@ -12,6 +12,7 @@ export interface SimpleProductUpdate_productUpdate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
attributes: string[] | null;
} }
export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_values { export interface SimpleProductUpdate_productUpdate_product_attributes_attribute_values {
@ -251,6 +252,7 @@ export interface SimpleProductUpdate_productVariantUpdate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
attributes: string[] | null;
} }
export interface SimpleProductUpdate_productVariantUpdate_productVariant_metadata { export interface SimpleProductUpdate_productVariantUpdate_productVariant_metadata {

View file

@ -12,6 +12,7 @@ export interface VariantCreate_productVariantCreate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
attributes: string[] | null;
} }
export interface VariantCreate_productVariantCreate_productVariant_metadata { export interface VariantCreate_productVariantCreate_productVariant_metadata {

View file

@ -12,6 +12,7 @@ export interface VariantUpdate_productVariantUpdate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
attributes: string[] | null;
} }
export interface VariantUpdate_productVariantUpdate_productVariant_metadata { export interface VariantUpdate_productVariantUpdate_productVariant_metadata {

View file

@ -42,7 +42,7 @@ export function createAttributeChangeHandler(
]); ]);
triggerChange(); triggerChange();
changeAttributeData(attributeId, [value]); changeAttributeData(attributeId, value === "" ? [] : [value]);
}; };
} }

View file

@ -8236,6 +8236,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-selected-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id MuiListItem-selected-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-selected-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id MuiListItem-selected-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AF"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8245,6 +8247,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AX"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8254,6 +8258,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AL"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8263,6 +8269,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="DZ"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8272,6 +8280,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AS"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8281,6 +8291,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AD"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8290,6 +8302,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AO"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8299,6 +8313,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AI"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8308,6 +8324,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AQ"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8317,6 +8335,8 @@ exports[`Storyshots Generics / Select with autocomplete can load more 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AG"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8388,6 +8408,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-selected-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id MuiListItem-selected-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-selected-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id MuiListItem-selected-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AF"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8397,6 +8419,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AX"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8406,6 +8430,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AL"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8415,6 +8441,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="DZ"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8424,6 +8452,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AS"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8433,6 +8463,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AD"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8442,6 +8474,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AO"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8451,6 +8485,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AI"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8460,6 +8496,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AQ"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8469,6 +8507,8 @@ exports[`Storyshots Generics / Select with autocomplete default 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AG"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8880,6 +8920,7 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option-add" data-test="singleautocomplete-select-option-add"
data-test-type="add"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8907,6 +8948,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-selected-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id MuiListItem-selected-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-selected-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id MuiListItem-selected-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AF"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8916,6 +8959,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AX"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8925,6 +8970,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AL"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8934,6 +8981,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="DZ"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8943,6 +8992,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AS"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8952,6 +9003,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AD"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8961,6 +9014,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AO"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8970,6 +9025,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AI"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8979,6 +9036,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AQ"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -8988,6 +9047,8 @@ exports[`Storyshots Generics / Select with autocomplete with add 1`] = `
aria-disabled="false" aria-disabled="false"
class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id" class="MuiButtonBase-root-id MuiListItem-root-id MuiMenuItem-root-id SingleAutocompleteSelectFieldContent-menuItem-id MuiMenuItem-gutters-id MuiListItem-gutters-id MuiListItem-button-id"
data-test="singleautocomplete-select-option" data-test="singleautocomplete-select-option"
data-test-type="option"
data-test-value="AG"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
@ -140044,6 +140105,152 @@ Ctrl + K"
<div <div
class="CardSpacer-spacer-id" class="CardSpacer-spacer-id"
/> />
<div
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id ProductAttributes-card-id MuiPaper-rounded-id"
>
<div
class="CardTitle-root-id"
>
<span
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
>
Attributes
</span>
<div
class="CardTitle-toolbar-id"
/>
</div>
<div
class="CardTitle-children-id"
/>
<hr
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id ProductAttributes-cardContent-id"
>
<div
class="ProductAttributes-expansionBar-id"
>
<div
class="ProductAttributes-expansionBarLabelContainer-id"
>
<div
class="MuiTypography-root-id ProductAttributes-expansionBarLabel-id MuiTypography-caption-id"
>
1 Attributes
</div>
</div>
<button
class="MuiButtonBase-root-id MuiIconButton-root-id ProductAttributes-expansionBarButton-id"
data-test="product-attributes-expand"
tabindex="0"
type="button"
>
<span
class="MuiIconButton-label-id"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id ProductAttributes-expansionBarButtonIcon-id ProductAttributes-rotate-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</span>
</button>
</div>
<hr
class="Hr-root-id"
/>
<div
class="ProductAttributes-attributeSection-id Grid-root-id Grid-uniform-id"
>
<div
class="ProductAttributes-attributeSectionLabel-id"
data-test="product-attribute-label"
>
<div
class="MuiTypography-root-id MuiTypography-body1-id"
>
Author
</div>
</div>
<div
data-test="product-attribute-value"
>
<div
class="SingleAutocompleteSelectField-container-id"
>
<div
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
>
<label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id"
data-shrink="false"
>
Value
</label>
<div
aria-autocomplete="list"
aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox"
>
<input
aria-invalid="true"
autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text"
value=""
/>
<div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root-id"
focusable="false"
role="presentation"
viewBox="0 0 24 24"
>
<g
style="fill-rule:evenodd"
>
<path
d="M7 10l5 5 5-5z"
/>
</g>
</svg>
</div>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
style="padding-left:8px"
>
<legend
class="PrivateNotchedOutline-legend-id"
style="width:0.01px"
>
<span>
</span>
</legend>
</fieldset>
</div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id"
>
Invalid value
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div <div
class="CardSpacer-spacer-id" class="CardSpacer-spacer-id"
/> />
@ -140252,8 +140459,8 @@ Ctrl + K"
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id" class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
> >
<label <label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id" class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id MuiFormLabel-filled-id"
data-shrink="false" data-shrink="true"
> >
Product Type Product Type
</label> </label>
@ -140268,7 +140475,7 @@ Ctrl + K"
autocomplete="off" autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id" class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text" type="text"
value="" value="Candy"
/> />
<div> <div>
<svg <svg
@ -140294,7 +140501,7 @@ Ctrl + K"
> >
<legend <legend
class="PrivateNotchedOutline-legend-id" class="PrivateNotchedOutline-legend-id"
style="width:0.01px" style="width:0"
> >
<span> <span>
@ -140303,7 +140510,7 @@ Ctrl + K"
</fieldset> </fieldset>
</div> </div>
<p <p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id" class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id MuiFormHelperText-filled-id"
> >
Invalid value Invalid value
</p> </p>
@ -140980,7 +141187,7 @@ exports[`Storyshots Views / Products / Create product variant add first variant
class="MuiTableRow-root-id" class="MuiTableRow-root-id"
> >
<td <td
class="MuiTableCell-root-id MuiTableCell-body-id TableCellAvatar-root-id ProductVariantNavigation-colAvatar-id ProductVariantNavigation-tabActive-id ProductVariantNavigation-noHandle-id" class="MuiTableCell-root-id MuiTableCell-body-id TableCellAvatar-root-id ProductVariantNavigation-colAvatar-id ProductVariantNavigation-tabActive-id ProductVariantNavigation-noHandle-id ProductVariantNavigation-firstVariant-id"
colspan="2" colspan="2"
> >
<div <div
@ -144473,7 +144680,7 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] =
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id" class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
> >
<label <label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id" class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id"
data-shrink="false" data-shrink="false"
> >
Color Color
@ -144481,11 +144688,11 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] =
<div <div
aria-autocomplete="list" aria-autocomplete="list"
aria-expanded="false" aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id" class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox" role="combobox"
> >
<input <input
aria-invalid="false" aria-invalid="true"
autocomplete="off" autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id" class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text" type="text"
@ -144523,17 +144730,17 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] =
</legend> </legend>
</fieldset> </fieldset>
</div> </div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id"
>
This field is required
</p>
</div> </div>
</div> </div>
</div> </div>
<div <div
class="FormSpacer-spacer-id" class="FormSpacer-spacer-id"
/> />
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id"
>
All attributes should have value
</div>
<div <div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id" class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id"
> >
@ -145969,7 +146176,7 @@ Ctrl + K"
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id" class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
> >
<label <label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-filled-id" class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id MuiFormLabel-filled-id"
data-shrink="true" data-shrink="true"
> >
Value Value
@ -145977,11 +146184,11 @@ Ctrl + K"
<div <div
aria-autocomplete="list" aria-autocomplete="list"
aria-expanded="false" aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id" class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox" role="combobox"
> >
<input <input
aria-invalid="false" aria-invalid="true"
autocomplete="off" autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id" class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text" type="text"
@ -146019,6 +146226,11 @@ Ctrl + K"
</legend> </legend>
</fieldset> </fieldset>
</div> </div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id MuiFormHelperText-filled-id"
>
Invalid value
</p>
</div> </div>
</div> </div>
</div> </div>
@ -173713,7 +173925,7 @@ exports[`Storyshots Views / Products / Product variant details attribute errors
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id" class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
> >
<label <label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-filled-id" class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id MuiFormLabel-filled-id"
data-shrink="true" data-shrink="true"
> >
Borders Borders
@ -173721,11 +173933,11 @@ exports[`Storyshots Views / Products / Product variant details attribute errors
<div <div
aria-autocomplete="list" aria-autocomplete="list"
aria-expanded="false" aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id" class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox" role="combobox"
> >
<input <input
aria-invalid="false" aria-invalid="true"
autocomplete="off" autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id" class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text" type="text"
@ -173763,6 +173975,11 @@ exports[`Storyshots Views / Products / Product variant details attribute errors
</legend> </legend>
</fieldset> </fieldset>
</div> </div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id MuiFormHelperText-filled-id"
>
This field is required
</p>
</div> </div>
</div> </div>
<div <div
@ -173829,11 +174046,6 @@ exports[`Storyshots Views / Products / Product variant details attribute errors
<div <div
class="FormSpacer-spacer-id" class="FormSpacer-spacer-id"
/> />
<div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id"
>
All attributes should have value
</div>
<div <div
class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id" class="MuiTypography-root-id MuiTypography-body1-id MuiTypography-colorError-id"
> >

View file

@ -68,13 +68,23 @@ storiesOf("Views / Products / Create product", module)
<ProductCreatePage <ProductCreatePage
currency="USD" currency="USD"
disabled={false} disabled={false}
errors={(["name", "productType", "category", "sku"] as Array< errors={([
keyof ProductCreatePageSubmitData "attributes",
>).map(field => ({ "name",
__typename: "ProductError", "productType",
code: ProductErrorCode.INVALID, "category",
field "sku"
}))} ] as Array<keyof ProductCreatePageSubmitData | "attributes">).map(
field => ({
__typename: "ProductError",
attributes:
field === "attributes"
? [productTypes[0].productAttributes[0].id]
: null,
code: ProductErrorCode.INVALID,
field
})
)}
header="Add product" header="Add product"
collections={product.collections} collections={product.collections}
fetchCategories={() => undefined} fetchCategories={() => undefined}
@ -83,6 +93,9 @@ storiesOf("Views / Products / Create product", module)
fetchMoreCategories={fetchMoreProps} fetchMoreCategories={fetchMoreProps}
fetchMoreCollections={fetchMoreProps} fetchMoreCollections={fetchMoreProps}
fetchMoreProductTypes={fetchMoreProps} fetchMoreProductTypes={fetchMoreProps}
initial={{
productType: productTypes[0].id
}}
productTypes={productTypes} productTypes={productTypes}
categories={[product.category]} categories={[product.category]}
onBack={() => undefined} onBack={() => undefined}

View file

@ -92,6 +92,7 @@ storiesOf("Views / Products / Product edit", module)
{...props} {...props}
product={{ product={{
...product, ...product,
productType: { productType: {
...product.productType, ...product.productType,
hasVariants: false hasVariants: false
@ -137,6 +138,7 @@ storiesOf("Views / Products / Product edit", module)
<ProductUpdatePage <ProductUpdatePage
{...props} {...props}
errors={([ errors={([
"attributes",
"basePrice", "basePrice",
"category", "category",
"chargeTaxes", "chargeTaxes",
@ -148,10 +150,16 @@ storiesOf("Views / Products / Product edit", module)
"seoTitle", "seoTitle",
"sku", "sku",
"stockQuantity" "stockQuantity"
] as Array<keyof ProductUpdatePageFormData>).map(field => ({ ] as Array<keyof ProductUpdatePageFormData | "attributes">).map(
__typename: "ProductError", field => ({
code: ProductErrorCode.INVALID, __typename: "ProductError",
field attributes:
}))} field === "attributes"
? [product.attributes[0].attribute.id]
: null,
code: ProductErrorCode.INVALID,
field
})
)}
/> />
)); ));

View file

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

View file

@ -705,6 +705,7 @@ export enum ProductErrorCode {
INVALID = "INVALID", INVALID = "INVALID",
NOT_FOUND = "NOT_FOUND", NOT_FOUND = "NOT_FOUND",
NOT_PRODUCTS_IMAGE = "NOT_PRODUCTS_IMAGE", NOT_PRODUCTS_IMAGE = "NOT_PRODUCTS_IMAGE",
NOT_PRODUCTS_VARIANT = "NOT_PRODUCTS_VARIANT",
REQUIRED = "REQUIRED", REQUIRED = "REQUIRED",
UNIQUE = "UNIQUE", UNIQUE = "UNIQUE",
VARIANT_NO_DIGITAL_CONTENT = "VARIANT_NO_DIGITAL_CONTENT", VARIANT_NO_DIGITAL_CONTENT = "VARIANT_NO_DIGITAL_CONTENT",

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: