Use error formatting in product section

This commit is contained in:
dominik-zeglen 2020-03-06 15:25:23 +01:00
parent c94ec7eafa
commit ff74c566ab
33 changed files with 348 additions and 244 deletions

View file

@ -27,7 +27,8 @@ import { SearchCollections_search_edges_node } from "@saleor/searches/types/Sear
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes"; import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import { FetchMoreProps, UserError } from "../../../types"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { FetchMoreProps } from "../../../types";
import { import {
createAttributeChangeHandler, createAttributeChangeHandler,
createAttributeMultiChangeHandler, createAttributeMultiChangeHandler,
@ -62,7 +63,7 @@ export interface ProductCreatePageSubmitData extends FormData {
} }
interface ProductCreatePageProps { interface ProductCreatePageProps {
errors: UserError[]; errors: ProductErrorFragment[];
collections: SearchCollections_search_edges_node[]; collections: SearchCollections_search_edges_node[];
categories: SearchCategories_search_edges_node[]; categories: SearchCategories_search_edges_node[];
currency: string; currency: string;
@ -227,6 +228,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
currency={currency} currency={currency}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -9,8 +9,8 @@ import CardTitle from "@saleor/components/CardTitle";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import RichTextEditor from "@saleor/components/RichTextEditor"; import RichTextEditor from "@saleor/components/RichTextEditor";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { UserError } from "@saleor/types"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import { getFieldError } from "@saleor/utils/errors"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
interface ProductDetailsFormProps { interface ProductDetailsFormProps {
data: { data: {
@ -18,7 +18,7 @@ interface ProductDetailsFormProps {
name: string; name: string;
}; };
disabled?: boolean; disabled?: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
// Draftail isn't controlled - it needs only initial input // Draftail isn't controlled - it needs only initial input
// because it's autosaving on its own. // because it's autosaving on its own.
// Ref https://github.com/mirumee/saleor/issues/4470 // Ref https://github.com/mirumee/saleor/issues/4470
@ -35,6 +35,8 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["name", "descriptionJson"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -42,8 +44,8 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
/> />
<CardContent> <CardContent>
<TextField <TextField
error={!!getFieldError(errors, "name")} error={!!formErrors.name}
helperText={getFieldError(errors, "name")?.message} helperText={getProductErrorMessage(formErrors.name, intl)}
disabled={disabled} disabled={disabled}
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
@ -57,8 +59,8 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
disabled={disabled} disabled={disabled}
error={!!getFieldError(errors, "descriptionJson")} error={!!formErrors.descriptionJson}
helperText={getFieldError(errors, "descriptionJson")?.message} helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
initial={initialDescription} initial={initialDescription}
label={intl.formatMessage(commonMessages.description)} label={intl.formatMessage(commonMessages.description)}
name="description" name="description"

View file

@ -17,8 +17,9 @@ import SingleAutocompleteSelectField, {
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import { ChangeEvent } from "@saleor/hooks/useForm"; import { ChangeEvent } from "@saleor/hooks/useForm";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { FetchMoreProps, UserError } from "@saleor/types"; import { FetchMoreProps } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
interface ProductType { interface ProductType {
hasVariants: boolean; hasVariants: boolean;
@ -54,7 +55,7 @@ interface ProductOrganizationProps {
productType?: string; productType?: string;
}; };
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
productType?: ProductType; productType?: ProductType;
productTypeInputDisplayValue?: string; productTypeInputDisplayValue?: string;
productTypes?: SingleAutocompleteChoiceType[]; productTypes?: SingleAutocompleteChoiceType[];
@ -96,6 +97,11 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
const classes = useStyles(props); const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(
["productType", "category", "collections"],
errors
);
return ( return (
<Card className={classes.card}> <Card className={classes.card}>
<CardTitle <CardTitle
@ -108,8 +114,8 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
{canChangeType ? ( {canChangeType ? (
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
displayValue={productTypeInputDisplayValue} displayValue={productTypeInputDisplayValue}
error={!!getFieldError(errors, "productType")} error={!!formErrors.productType}
helperText={getFieldError(errors, "productType")?.message} helperText={getProductErrorMessage(formErrors.productType, intl)}
name="productType" name="productType"
disabled={disabled} disabled={disabled}
label={intl.formatMessage({ label={intl.formatMessage({
@ -154,8 +160,8 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
<FormSpacer /> <FormSpacer />
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
displayValue={categoryInputDisplayValue} displayValue={categoryInputDisplayValue}
error={!!getFieldError(errors, "category")} error={!!formErrors.category}
helperText={getFieldError(errors, "category")?.message} helperText={getProductErrorMessage(formErrors.category, intl)}
disabled={disabled} disabled={disabled}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Category" defaultMessage: "Category"
@ -173,17 +179,21 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
<FormSpacer /> <FormSpacer />
<MultiAutocompleteSelectField <MultiAutocompleteSelectField
displayValues={collectionsInputDisplayValue} displayValues={collectionsInputDisplayValue}
error={!!formErrors.collections}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Collections" defaultMessage: "Collections"
})} })}
choices={disabled ? [] : collections} choices={disabled ? [] : collections}
name="collections" name="collections"
value={data.collections} value={data.collections}
helperText={intl.formatMessage({ helperText={
defaultMessage: getProductErrorMessage(formErrors.collections, intl) ||
"*Optional. Adding product to collection helps users find it.", intl.formatMessage({
description: "field is optional" defaultMessage:
})} "*Optional. Adding product to collection helps users find it.",
description: "field is optional"
})
}
onChange={onCollectionChange} onChange={onCollectionChange}
fetchChoices={fetchCollections} fetchChoices={fetchCollections}
data-tc="collections" data-tc="collections"

View file

@ -7,6 +7,8 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import PriceField from "@saleor/components/PriceField"; import PriceField from "@saleor/components/PriceField";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -26,15 +28,18 @@ interface ProductPricingProps {
basePrice: number; basePrice: number;
}; };
disabled: boolean; disabled: boolean;
errors: ProductErrorFragment[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
const ProductPricing: React.FC<ProductPricingProps> = props => { const ProductPricing: React.FC<ProductPricingProps> = props => {
const { currency, data, disabled, onChange } = props; const { currency, data, disabled, errors, onChange } = props;
const classes = useStyles(props);
const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["basePrice"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -61,10 +66,17 @@ const ProductPricing: React.FC<ProductPricingProps> = props => {
defaultMessage: "Price", defaultMessage: "Price",
description: "product price" description: "product price"
})} })}
error={!!formErrors.basePrice}
hint={getProductErrorMessage(formErrors.basePrice, intl)}
name="basePrice" name="basePrice"
value={data.basePrice} value={data.basePrice}
currencySymbol={currency} currencySymbol={currency}
onChange={onChange} onChange={onChange}
InputProps={{
inputProps: {
min: 0
}
}}
/> />
</div> </div>
</CardContent> </CardContent>

View file

@ -6,8 +6,8 @@ import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { UserError } from "@saleor/types"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { getFieldError } from "@saleor/utils/errors"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ProductDetails_product } from "../../types/ProductDetails"; import { ProductDetails_product } from "../../types/ProductDetails";
@ -28,17 +28,19 @@ interface ProductStockProps {
stockQuantity: number; stockQuantity: number;
}; };
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
product: ProductDetails_product; product: ProductDetails_product;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
const ProductStock: React.FC<ProductStockProps> = props => { const ProductStock: React.FC<ProductStockProps> = props => {
const { data, disabled, product, onChange, errors } = props; const { data, disabled, product, onChange, errors } = props;
const classes = useStyles(props);
const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["sku", "stockQuantity"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -58,8 +60,8 @@ const ProductStock: React.FC<ProductStockProps> = props => {
})} })}
value={data.sku} value={data.sku}
onChange={onChange} onChange={onChange}
error={!!getFieldError(errors, "sku")} error={!!formErrors.sku}
helperText={getFieldError(errors, "sku")?.message} helperText={getProductErrorMessage(formErrors.sku, intl)}
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
@ -73,19 +75,17 @@ const ProductStock: React.FC<ProductStockProps> = props => {
type="number" type="number"
onChange={onChange} onChange={onChange}
helperText={ helperText={
product getProductErrorMessage(formErrors.stockQuantity, intl) ||
? intl.formatMessage( (product &&
{ intl.formatMessage(
defaultMessage: "Allocated: {quantity}", {
description: "allocated product stock" defaultMessage: "Allocated: {quantity}",
}, description: "allocated product stock"
{ },
quantity: maybe( {
() => product.variants[0].quantityAllocated quantity: product?.variants[0].quantityAllocated
) }
} ))
)
: undefined
} }
/> />
</div> </div>

View file

@ -19,9 +19,10 @@ import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories"; import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections"; import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import { FetchMoreProps, ListActions, UserError } from "@saleor/types"; import { FetchMoreProps, ListActions } from "@saleor/types";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { import {
ProductDetails_product, ProductDetails_product,
ProductDetails_product_images, ProductDetails_product_images,
@ -48,7 +49,7 @@ import ProductStock from "../ProductStock";
import ProductVariants from "../ProductVariants"; import ProductVariants from "../ProductVariants";
export interface ProductUpdatePageProps extends ListActions { export interface ProductUpdatePageProps extends ListActions {
errors: UserError[]; errors: ProductErrorFragment[];
placeholderImage: string; placeholderImage: string;
collections: SearchCollections_search_edges_node[]; collections: SearchCollections_search_edges_node[];
categories: SearchCategories_search_edges_node[]; categories: SearchCategories_search_edges_node[];
@ -219,6 +220,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
currency={currency} currency={currency}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -10,12 +10,13 @@ import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import { maybe } from "@saleor/misc"; import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate";
import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate";
import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes";
import { getFormErrors } from "@saleor/utils/errors";
import { getBulkProductErrorMessage } from "@saleor/utils/errors/product";
import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails"; import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails";
import { ProductVariantCreateFormData } from "./form"; import { ProductVariantCreateFormData } from "./form";
import { VariantField } from "./reducer"; import { VariantField } from "./reducer";
@ -24,7 +25,7 @@ export interface ProductVariantCreateSummaryProps {
attributes: ProductDetails_product_productType_variantAttributes[]; attributes: ProductDetails_product_productType_variantAttributes[];
currencySymbol: string; currencySymbol: string;
data: ProductVariantCreateFormData; data: ProductVariantCreateFormData;
errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[];
onVariantDataChange: ( onVariantDataChange: (
variantIndex: number, variantIndex: number,
field: VariantField, field: VariantField,
@ -107,9 +108,7 @@ function getVariantName(
); );
} }
const ProductVariantCreateSummary: React.FC< const ProductVariantCreateSummary: React.FC<ProductVariantCreateSummaryProps> = props => {
ProductVariantCreateSummaryProps
> = props => {
const { const {
attributes, attributes,
currencySymbol, currencySymbol,
@ -119,6 +118,7 @@ const ProductVariantCreateSummary: React.FC<
onVariantDelete onVariantDelete
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
const intl = useIntl();
return ( return (
<> <>
@ -181,6 +181,10 @@ const ProductVariantCreateSummary: React.FC<
const variantErrors = errors.filter( const variantErrors = errors.filter(
error => error.index === variantIndex error => error.index === variantIndex
); );
const variantFormErrors = getFormErrors(
["priceOverride", "quantity", "sku"],
variantErrors
);
return ( return (
<div <div
@ -212,16 +216,10 @@ const ProductVariantCreateSummary: React.FC<
endAdornment: currencySymbol endAdornment: currencySymbol
}} }}
className={classes.input} className={classes.input}
error={ error={!!variantFormErrors.priceOverride}
!!variantErrors.find( helperText={getBulkProductErrorMessage(
error => error.field === "priceOverride" variantFormErrors.priceOverride,
) intl
}
helperText={maybe(
() =>
variantErrors.find(
error => error.field === "priceOverride"
).message
)} )}
inputProps={{ inputProps={{
min: 0, min: 0,
@ -241,13 +239,10 @@ const ProductVariantCreateSummary: React.FC<
<div className={classNames(classes.col, classes.colStock)}> <div className={classNames(classes.col, classes.colStock)}>
<TextField <TextField
className={classes.input} className={classes.input}
error={ error={!!variantFormErrors.quantity}
!!variantErrors.find(error => error.field === "quantity") helperText={getBulkProductErrorMessage(
} variantFormErrors.quantity,
helperText={maybe( intl
() =>
variantErrors.find(error => error.field === "quantity")
.message
)} )}
inputProps={{ inputProps={{
min: 0, min: 0,
@ -267,10 +262,10 @@ const ProductVariantCreateSummary: React.FC<
<div className={classNames(classes.col, classes.colSku)}> <div className={classNames(classes.col, classes.colSku)}>
<TextField <TextField
className={classes.input} className={classes.input}
error={!!variantErrors.find(error => error.field === "sku")} error={!!variantFormErrors.sku}
helperText={maybe( helperText={getBulkProductErrorMessage(
() => variantFormErrors.sku,
variantErrors.find(error => error.field === "sku").message intl
)} )}
fullWidth fullWidth
value={variant.sku} value={variant.sku}

View file

@ -13,8 +13,8 @@ import useFormset, {
FormsetChange, FormsetChange,
FormsetData FormsetData
} from "@saleor/hooks/useFormset"; } from "@saleor/hooks/useFormset";
import { VariantCreate_productVariantCreate_productErrors } from "@saleor/products/types/VariantCreate";
import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData"; import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData";
import ProductVariantAttributes, { import ProductVariantAttributes, {
@ -39,7 +39,7 @@ export interface ProductVariantCreatePageSubmitData
interface ProductVariantCreatePageProps { interface ProductVariantCreatePageProps {
currencySymbol: string; currencySymbol: string;
errors: VariantCreate_productVariantCreate_productErrors[]; errors: ProductErrorFragment[];
header: string; header: string;
loading: boolean; loading: boolean;
product: ProductVariantCreateData_product; product: ProductVariantCreateData_product;

View file

@ -6,8 +6,8 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import PriceField from "@saleor/components/PriceField"; import PriceField from "@saleor/components/PriceField";
import { UserError } from "@saleor/types"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { getFieldError } from "@saleor/utils/errors"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -24,7 +24,7 @@ interface ProductVariantPriceProps {
currencySymbol?: string; currencySymbol?: string;
priceOverride?: string; priceOverride?: string;
costPrice?: string; costPrice?: string;
errors: UserError[]; errors: ProductErrorFragment[];
loading?: boolean; loading?: boolean;
onChange(event: any); onChange(event: any);
} }
@ -38,10 +38,12 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
loading, loading,
onChange onChange
} = props; } = props;
const classes = useStyles(props);
const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["price_override", "cost_price"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -54,13 +56,13 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
<div className={classes.grid}> <div className={classes.grid}>
<div> <div>
<PriceField <PriceField
error={!!getFieldError(errors, "price_override")} error={!!formErrors.price_override}
name="priceOverride" name="priceOverride"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Selling price override" defaultMessage: "Selling price override"
})} })}
hint={ hint={
getFieldError(errors, "price_override")?.message || getProductErrorMessage(formErrors.price_override, intl) ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: "Optional", defaultMessage: "Optional",
description: "optional field", description: "optional field",
@ -75,13 +77,13 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
</div> </div>
<div> <div>
<PriceField <PriceField
error={!!getFieldError(errors, "cost_price")} error={!!formErrors.cost_price}
name="costPrice" name="costPrice"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Cost price override" defaultMessage: "Cost price override"
})} })}
hint={ hint={
getFieldError(errors, "cost_price")?.message || getProductErrorMessage(formErrors.cost_price, intl) ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: "Optional", defaultMessage: "Optional",
description: "optional field", description: "optional field",

View file

@ -6,8 +6,8 @@ import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { UserError } from "@saleor/types"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import { getFieldError } from "@saleor/utils/errors"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -21,7 +21,7 @@ const useStyles = makeStyles(
); );
interface ProductVariantStockProps { interface ProductVariantStockProps {
errors: UserError[]; errors: ProductErrorFragment[];
sku: string; sku: string;
quantity: string; quantity: string;
stockAllocated?: number; stockAllocated?: number;
@ -31,10 +31,12 @@ interface ProductVariantStockProps {
const ProductVariantStock: React.FC<ProductVariantStockProps> = props => { const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
const { errors, sku, quantity, stockAllocated, loading, onChange } = props; const { errors, sku, quantity, stockAllocated, loading, onChange } = props;
const classes = useStyles(props);
const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["quantity", "sku"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -47,7 +49,7 @@ const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
<div className={classes.grid}> <div className={classes.grid}>
<div> <div>
<TextField <TextField
error={!!getFieldError(errors, "quantity")} error={!!formErrors.quantity}
name="quantity" name="quantity"
value={quantity} value={quantity}
label={intl.formatMessage({ label={intl.formatMessage({
@ -55,19 +57,17 @@ const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
description: "product variant stock" description: "product variant stock"
})} })}
helperText={ helperText={
getFieldError(errors, "quantity") getProductErrorMessage(formErrors.quantity, intl) ||
? getFieldError(errors, "quantity") (!!stockAllocated &&
: !!stockAllocated intl.formatMessage(
? intl.formatMessage( {
{ defaultMessage: "Allocated: {quantity}",
defaultMessage: "Allocated: {quantity}", description: "variant allocated stock"
description: "variant allocated stock" },
}, {
{ quantity: stockAllocated
quantity: stockAllocated }
} ))
)
: undefined
} }
onChange={onChange} onChange={onChange}
disabled={loading} disabled={loading}
@ -76,8 +76,8 @@ const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
</div> </div>
<div> <div>
<TextField <TextField
error={!!getFieldError(errors, "sku")} error={!!formErrors.sku}
helperText={getFieldError(errors, "sku")?.message} helperText={getProductErrorMessage(formErrors.sku, intl)}
name="sku" name="sku"
value={sku} value={sku}
label={intl.formatMessage({ label={intl.formatMessage({

View file

@ -1,5 +1,6 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import { productErrorFragment } from "@saleor/attributes/mutations";
import { TypedMutation } from "../mutations"; import { TypedMutation } from "../mutations";
import { ProductCreate, ProductCreateVariables } from "./types/ProductCreate"; import { ProductCreate, ProductCreateVariables } from "./types/ProductCreate";
import { ProductDelete, ProductDeleteVariables } from "./types/ProductDelete"; import { ProductDelete, ProductDeleteVariables } from "./types/ProductDelete";
@ -54,13 +55,21 @@ import {
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
} from "./types/ProductVariantBulkDelete"; } from "./types/ProductVariantBulkDelete";
export const bulkProductErrorFragment = gql`
fragment BulkProductErrorFragment on BulkProductError {
field
code
index
}
`;
export const productImageCreateMutation = gql` export const productImageCreateMutation = gql`
${productErrorFragment}
${productFragmentDetails} ${productFragmentDetails}
mutation ProductImageCreate($product: ID!, $image: Upload!, $alt: String) { mutation ProductImageCreate($product: ID!, $image: Upload!, $alt: String) {
productImageCreate(input: { alt: $alt, image: $image, product: $product }) { productImageCreate(input: { alt: $alt, image: $image, product: $product }) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
...Product ...Product
@ -74,11 +83,11 @@ export const TypedProductImageCreateMutation = TypedMutation<
>(productImageCreateMutation); >(productImageCreateMutation);
export const productDeleteMutation = gql` export const productDeleteMutation = gql`
${productErrorFragment}
mutation ProductDelete($id: ID!) { mutation ProductDelete($id: ID!) {
productDelete(id: $id) { productDelete(id: $id) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
id id
@ -92,11 +101,11 @@ export const TypedProductDeleteMutation = TypedMutation<
>(productDeleteMutation); >(productDeleteMutation);
export const productImagesReorder = gql` export const productImagesReorder = gql`
${productErrorFragment}
mutation ProductImageReorder($productId: ID!, $imagesIds: [ID]!) { mutation ProductImageReorder($productId: ID!, $imagesIds: [ID]!) {
productImageReorder(productId: $productId, imagesIds: $imagesIds) { productImageReorder(productId: $productId, imagesIds: $imagesIds) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
id id
@ -116,6 +125,7 @@ export const TypedProductImagesReorder = TypedMutation<
>(productImagesReorder); >(productImagesReorder);
export const productUpdateMutation = gql` export const productUpdateMutation = gql`
${productErrorFragment}
${productFragmentDetails} ${productFragmentDetails}
mutation ProductUpdate( mutation ProductUpdate(
$id: ID! $id: ID!
@ -145,9 +155,8 @@ export const productUpdateMutation = gql`
seo: $seo seo: $seo
} }
) { ) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
...Product ...Product
@ -161,6 +170,7 @@ export const TypedProductUpdateMutation = TypedMutation<
>(productUpdateMutation); >(productUpdateMutation);
export const simpleProductUpdateMutation = gql` export const simpleProductUpdateMutation = gql`
${productErrorFragment}
${productFragmentDetails} ${productFragmentDetails}
${fragmentVariant} ${fragmentVariant}
mutation SimpleProductUpdate( mutation SimpleProductUpdate(
@ -193,18 +203,16 @@ export const simpleProductUpdateMutation = gql`
seo: $seo seo: $seo
} }
) { ) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
...Product ...Product
} }
} }
productVariantUpdate(id: $productVariantId, input: $productVariantInput) { productVariantUpdate(id: $productVariantId, input: $productVariantInput) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -218,6 +226,7 @@ export const TypedSimpleProductUpdateMutation = TypedMutation<
>(simpleProductUpdateMutation); >(simpleProductUpdateMutation);
export const productCreateMutation = gql` export const productCreateMutation = gql`
${productErrorFragment}
${productFragmentDetails} ${productFragmentDetails}
mutation ProductCreate( mutation ProductCreate(
$attributes: [AttributeValueInput] $attributes: [AttributeValueInput]
@ -251,9 +260,8 @@ export const productCreateMutation = gql`
seo: $seo seo: $seo
} }
) { ) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
...Product ...Product
@ -267,11 +275,11 @@ export const TypedProductCreateMutation = TypedMutation<
>(productCreateMutation); >(productCreateMutation);
export const variantDeleteMutation = gql` export const variantDeleteMutation = gql`
${productErrorFragment}
mutation VariantDelete($id: ID!) { mutation VariantDelete($id: ID!) {
productVariantDelete(id: $id) { productVariantDelete(id: $id) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
productVariant { productVariant {
id id
@ -286,6 +294,7 @@ export const TypedVariantDeleteMutation = TypedMutation<
export const variantUpdateMutation = gql` export const variantUpdateMutation = gql`
${fragmentVariant} ${fragmentVariant}
${productErrorFragment}
mutation VariantUpdate( mutation VariantUpdate(
$id: ID! $id: ID!
$attributes: [AttributeValueInput] $attributes: [AttributeValueInput]
@ -306,10 +315,8 @@ export const variantUpdateMutation = gql`
trackInventory: $trackInventory trackInventory: $trackInventory
} }
) { ) {
productErrors { errors: productErrors {
code ...ProductErrorFragment
field
message
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -324,12 +331,11 @@ export const TypedVariantUpdateMutation = TypedMutation<
export const variantCreateMutation = gql` export const variantCreateMutation = gql`
${fragmentVariant} ${fragmentVariant}
${productErrorFragment}
mutation VariantCreate($input: ProductVariantCreateInput!) { mutation VariantCreate($input: ProductVariantCreateInput!) {
productVariantCreate(input: $input) { productVariantCreate(input: $input) {
productErrors { errors: productErrors {
code ...ProductErrorFragment
field
message
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -343,8 +349,12 @@ export const TypedVariantCreateMutation = TypedMutation<
>(variantCreateMutation); >(variantCreateMutation);
export const productImageDeleteMutation = gql` export const productImageDeleteMutation = gql`
${productErrorFragment}
mutation ProductImageDelete($id: ID!) { mutation ProductImageDelete($id: ID!) {
productImageDelete(id: $id) { productImageDelete(id: $id) {
errors: productErrors {
...ProductErrorFragment
}
product { product {
id id
images { images {
@ -360,12 +370,12 @@ export const TypedProductImageDeleteMutation = TypedMutation<
>(productImageDeleteMutation); >(productImageDeleteMutation);
export const productImageUpdateMutation = gql` export const productImageUpdateMutation = gql`
${productErrorFragment}
${productFragmentDetails} ${productFragmentDetails}
mutation ProductImageUpdate($id: ID!, $alt: String!) { mutation ProductImageUpdate($id: ID!, $alt: String!) {
productImageUpdate(id: $id, input: { alt: $alt }) { productImageUpdate(id: $id, input: { alt: $alt }) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
product { product {
...Product ...Product
@ -380,11 +390,11 @@ export const TypedProductImageUpdateMutation = TypedMutation<
export const variantImageAssignMutation = gql` export const variantImageAssignMutation = gql`
${fragmentVariant} ${fragmentVariant}
${productErrorFragment}
mutation VariantImageAssign($variantId: ID!, $imageId: ID!) { mutation VariantImageAssign($variantId: ID!, $imageId: ID!) {
variantImageAssign(variantId: $variantId, imageId: $imageId) { variantImageAssign(variantId: $variantId, imageId: $imageId) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -399,11 +409,11 @@ export const TypedVariantImageAssignMutation = TypedMutation<
export const variantImageUnassignMutation = gql` export const variantImageUnassignMutation = gql`
${fragmentVariant} ${fragmentVariant}
${productErrorFragment}
mutation VariantImageUnassign($variantId: ID!, $imageId: ID!) { mutation VariantImageUnassign($variantId: ID!, $imageId: ID!) {
variantImageUnassign(variantId: $variantId, imageId: $imageId) { variantImageUnassign(variantId: $variantId, imageId: $imageId) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
productVariant { productVariant {
...ProductVariant ...ProductVariant
@ -417,11 +427,11 @@ export const TypedVariantImageUnassignMutation = TypedMutation<
>(variantImageUnassignMutation); >(variantImageUnassignMutation);
export const productBulkDeleteMutation = gql` export const productBulkDeleteMutation = gql`
${productErrorFragment}
mutation productBulkDelete($ids: [ID!]!) { mutation productBulkDelete($ids: [ID!]!) {
productBulkDelete(ids: $ids) { productBulkDelete(ids: $ids) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
} }
} }
@ -432,11 +442,11 @@ export const TypedProductBulkDeleteMutation = TypedMutation<
>(productBulkDeleteMutation); >(productBulkDeleteMutation);
export const productBulkPublishMutation = gql` export const productBulkPublishMutation = gql`
${productErrorFragment}
mutation productBulkPublish($ids: [ID!]!, $isPublished: Boolean!) { mutation productBulkPublish($ids: [ID!]!, $isPublished: Boolean!) {
productBulkPublish(ids: $ids, isPublished: $isPublished) { productBulkPublish(ids: $ids, isPublished: $isPublished) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
} }
} }
@ -447,20 +457,14 @@ export const TypedProductBulkPublishMutation = TypedMutation<
>(productBulkPublishMutation); >(productBulkPublishMutation);
export const ProductVariantBulkCreateMutation = gql` export const ProductVariantBulkCreateMutation = gql`
${bulkProductErrorFragment}
mutation ProductVariantBulkCreate( mutation ProductVariantBulkCreate(
$id: ID! $id: ID!
$inputs: [ProductVariantBulkCreateInput]! $inputs: [ProductVariantBulkCreateInput]!
) { ) {
productVariantBulkCreate(product: $id, variants: $inputs) { productVariantBulkCreate(product: $id, variants: $inputs) {
bulkProductErrors { errors: bulkProductErrors {
field ...BulkProductErrorFragment
message
code
index
}
errors {
field
message
} }
} }
} }
@ -471,11 +475,11 @@ export const TypedProductVariantBulkCreateMutation = TypedMutation<
>(ProductVariantBulkCreateMutation); >(ProductVariantBulkCreateMutation);
export const ProductVariantBulkDeleteMutation = gql` export const ProductVariantBulkDeleteMutation = gql`
${productErrorFragment}
mutation ProductVariantBulkDelete($ids: [ID!]!) { mutation ProductVariantBulkDelete($ids: [ID!]!) {
productVariantBulkDelete(ids: $ids) { productVariantBulkDelete(ids: $ids) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
} }
} }

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: BulkProductErrorFragment
// ====================================================
export interface BulkProductErrorFragment {
__typename: "BulkProductError";
field: string | null;
code: ProductErrorCode;
index: number | null;
}

View file

@ -2,16 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeValueInput, SeoInput, AttributeInputTypeEnum } from "./../../types/globalTypes"; import { AttributeValueInput, SeoInput, ProductErrorCode, AttributeInputTypeEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductCreate // GraphQL mutation operation: ProductCreate
// ==================================================== // ====================================================
export interface ProductCreate_productCreate_errors { export interface ProductCreate_productCreate_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductCreate_productCreate_product_category { export interface ProductCreate_productCreate_product_category {

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductDelete // GraphQL mutation operation: ProductDelete
// ==================================================== // ====================================================
export interface ProductDelete_productDelete_errors { export interface ProductDelete_productDelete_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductDelete_productDelete_product { export interface ProductDelete_productDelete_product {

View file

@ -2,16 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeInputTypeEnum } from "./../../types/globalTypes"; import { ProductErrorCode, AttributeInputTypeEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductImageCreate // GraphQL mutation operation: ProductImageCreate
// ==================================================== // ====================================================
export interface ProductImageCreate_productImageCreate_errors { export interface ProductImageCreate_productImageCreate_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductImageCreate_productImageCreate_product_category { export interface ProductImageCreate_productImageCreate_product_category {

View file

@ -2,10 +2,18 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductImageDelete // GraphQL mutation operation: ProductImageDelete
// ==================================================== // ====================================================
export interface ProductImageDelete_productImageDelete_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface ProductImageDelete_productImageDelete_product_images { export interface ProductImageDelete_productImageDelete_product_images {
__typename: "ProductImage"; __typename: "ProductImage";
id: string; id: string;
@ -19,6 +27,7 @@ export interface ProductImageDelete_productImageDelete_product {
export interface ProductImageDelete_productImageDelete { export interface ProductImageDelete_productImageDelete {
__typename: "ProductImageDelete"; __typename: "ProductImageDelete";
errors: ProductImageDelete_productImageDelete_errors[];
product: ProductImageDelete_productImageDelete_product | null; product: ProductImageDelete_productImageDelete_product | null;
} }

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductImageReorder // GraphQL mutation operation: ProductImageReorder
// ==================================================== // ====================================================
export interface ProductImageReorder_productImageReorder_errors { export interface ProductImageReorder_productImageReorder_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductImageReorder_productImageReorder_product_images { export interface ProductImageReorder_productImageReorder_product_images {

View file

@ -2,16 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeInputTypeEnum } from "./../../types/globalTypes"; import { ProductErrorCode, AttributeInputTypeEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductImageUpdate // GraphQL mutation operation: ProductImageUpdate
// ==================================================== // ====================================================
export interface ProductImageUpdate_productImageUpdate_errors { export interface ProductImageUpdate_productImageUpdate_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductImageUpdate_productImageUpdate_product_category { export interface ProductImageUpdate_productImageUpdate_product_category {

View file

@ -2,16 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeValueInput, SeoInput, AttributeInputTypeEnum } from "./../../types/globalTypes"; import { AttributeValueInput, SeoInput, ProductErrorCode, AttributeInputTypeEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductUpdate // GraphQL mutation operation: ProductUpdate
// ==================================================== // ====================================================
export interface ProductUpdate_productUpdate_errors { export interface ProductUpdate_productUpdate_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductUpdate_productUpdate_product_category { export interface ProductUpdate_productUpdate_product_category {

View file

@ -8,23 +8,15 @@ import { ProductVariantBulkCreateInput, ProductErrorCode } from "./../../types/g
// GraphQL mutation operation: ProductVariantBulkCreate // GraphQL mutation operation: ProductVariantBulkCreate
// ==================================================== // ====================================================
export interface ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors { export interface ProductVariantBulkCreate_productVariantBulkCreate_errors {
__typename: "BulkProductError"; __typename: "BulkProductError";
field: string | null; field: string | null;
message: string | null;
code: ProductErrorCode; code: ProductErrorCode;
index: number | null; index: number | null;
} }
export interface ProductVariantBulkCreate_productVariantBulkCreate_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface ProductVariantBulkCreate_productVariantBulkCreate { export interface ProductVariantBulkCreate_productVariantBulkCreate {
__typename: "ProductVariantBulkCreate"; __typename: "ProductVariantBulkCreate";
bulkProductErrors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[];
errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[]; errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[];
} }

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: ProductVariantBulkDelete // GraphQL mutation operation: ProductVariantBulkDelete
// ==================================================== // ====================================================
export interface ProductVariantBulkDelete_productVariantBulkDelete_errors { export interface ProductVariantBulkDelete_productVariantBulkDelete_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface ProductVariantBulkDelete_productVariantBulkDelete { export interface ProductVariantBulkDelete_productVariantBulkDelete {

View file

@ -2,16 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeValueInput, ProductVariantInput, SeoInput, AttributeInputTypeEnum } from "./../../types/globalTypes"; import { AttributeValueInput, ProductVariantInput, SeoInput, ProductErrorCode, AttributeInputTypeEnum } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: SimpleProductUpdate // GraphQL mutation operation: SimpleProductUpdate
// ==================================================== // ====================================================
export interface SimpleProductUpdate_productUpdate_errors { export interface SimpleProductUpdate_productUpdate_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface SimpleProductUpdate_productUpdate_product_category { export interface SimpleProductUpdate_productUpdate_product_category {
@ -183,9 +183,9 @@ export interface SimpleProductUpdate_productUpdate {
} }
export interface SimpleProductUpdate_productVariantUpdate_errors { export interface SimpleProductUpdate_productVariantUpdate_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface SimpleProductUpdate_productVariantUpdate_productVariant_attributes_attribute_values { export interface SimpleProductUpdate_productVariantUpdate_productVariant_attributes_attribute_values {

View file

@ -8,11 +8,10 @@ import { ProductVariantCreateInput, ProductErrorCode } from "./../../types/globa
// GraphQL mutation operation: VariantCreate // GraphQL mutation operation: VariantCreate
// ==================================================== // ====================================================
export interface VariantCreate_productVariantCreate_productErrors { export interface VariantCreate_productVariantCreate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface VariantCreate_productVariantCreate_productVariant_attributes_attribute_values { export interface VariantCreate_productVariantCreate_productVariant_attributes_attribute_values {
@ -114,7 +113,7 @@ export interface VariantCreate_productVariantCreate_productVariant {
export interface VariantCreate_productVariantCreate { export interface VariantCreate_productVariantCreate {
__typename: "ProductVariantCreate"; __typename: "ProductVariantCreate";
productErrors: VariantCreate_productVariantCreate_productErrors[]; errors: VariantCreate_productVariantCreate_errors[];
productVariant: VariantCreate_productVariantCreate_productVariant | null; productVariant: VariantCreate_productVariantCreate_productVariant | null;
} }

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: VariantDelete // GraphQL mutation operation: VariantDelete
// ==================================================== // ====================================================
export interface VariantDelete_productVariantDelete_errors { export interface VariantDelete_productVariantDelete_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface VariantDelete_productVariantDelete_productVariant { export interface VariantDelete_productVariantDelete_productVariant {

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: VariantImageAssign // GraphQL mutation operation: VariantImageAssign
// ==================================================== // ====================================================
export interface VariantImageAssign_variantImageAssign_errors { export interface VariantImageAssign_variantImageAssign_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface VariantImageAssign_variantImageAssign_productVariant_attributes_attribute_values { export interface VariantImageAssign_variantImageAssign_productVariant_attributes_attribute_values {

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: VariantImageUnassign // GraphQL mutation operation: VariantImageUnassign
// ==================================================== // ====================================================
export interface VariantImageUnassign_variantImageUnassign_errors { export interface VariantImageUnassign_variantImageUnassign_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface VariantImageUnassign_variantImageUnassign_productVariant_attributes_attribute_values { export interface VariantImageUnassign_variantImageUnassign_productVariant_attributes_attribute_values {

View file

@ -8,11 +8,10 @@ import { AttributeValueInput, ProductErrorCode } from "./../../types/globalTypes
// GraphQL mutation operation: VariantUpdate // GraphQL mutation operation: VariantUpdate
// ==================================================== // ====================================================
export interface VariantUpdate_productVariantUpdate_productErrors { export interface VariantUpdate_productVariantUpdate_errors {
__typename: "ProductError"; __typename: "ProductError";
code: ProductErrorCode; code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface VariantUpdate_productVariantUpdate_productVariant_attributes_attribute_values { export interface VariantUpdate_productVariantUpdate_productVariant_attributes_attribute_values {
@ -114,7 +113,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant {
export interface VariantUpdate_productVariantUpdate { export interface VariantUpdate_productVariantUpdate {
__typename: "ProductVariantUpdate"; __typename: "ProductVariantUpdate";
productErrors: VariantUpdate_productVariantUpdate_productErrors[]; errors: VariantUpdate_productVariantUpdate_errors[];
productVariant: VariantUpdate_productVariantUpdate_productVariant | null; productVariant: VariantUpdate_productVariantUpdate_productVariant | null;
} }

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: productBulkDelete // GraphQL mutation operation: productBulkDelete
// ==================================================== // ====================================================
export interface productBulkDelete_productBulkDelete_errors { export interface productBulkDelete_productBulkDelete_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface productBulkDelete_productBulkDelete { export interface productBulkDelete_productBulkDelete {

View file

@ -2,14 +2,16 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: productBulkPublish // GraphQL mutation operation: productBulkPublish
// ==================================================== // ====================================================
export interface productBulkPublish_productBulkPublish_errors { export interface productBulkPublish_productBulkPublish_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface productBulkPublish_productBulkPublish { export interface productBulkPublish_productBulkPublish {

View file

@ -58,13 +58,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = () => {
}) })
}); });
navigate(productUrl(data.productCreate.product.id)); navigate(productUrl(data.productCreate.product.id));
} else {
const attributeError = data.productCreate.errors.find(
err => err.field === "attributes"
);
if (!!attributeError) {
notify({ text: attributeError.message });
}
} }
}; };
@ -120,10 +113,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = () => {
[] []
).map(edge => edge.node)} ).map(edge => edge.node)}
disabled={productCreateOpts.loading} disabled={productCreateOpts.loading}
errors={maybe( errors={productCreateOpts.data?.productCreate.errors || []}
() => productCreateOpts.data.productCreate.errors,
[]
)}
fetchCategories={searchCategory} fetchCategories={searchCategory}
fetchCollections={searchCollection} fetchCollections={searchCollection}
fetchProductTypes={searchProductTypes} fetchProductTypes={searchProductTypes}

View file

@ -19,6 +19,7 @@ import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import { ProductErrorCode } from "@saleor/types/globalTypes";
import { getMutationState, maybe } from "../../../misc"; import { getMutationState, maybe } from "../../../misc";
import ProductUpdatePage from "../../components/ProductUpdatePage"; import ProductUpdatePage from "../../components/ProductUpdatePage";
import ProductUpdateOperations from "../../containers/ProductUpdateOperations"; import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
@ -101,13 +102,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
notify({ notify({
text: intl.formatMessage(commonMessages.savedChanges) text: intl.formatMessage(commonMessages.savedChanges)
}); });
} else {
const attributeError = data.productUpdate.errors.find(
err => err.field === "attributes"
);
if (!!attributeError) {
notify({ text: attributeError.message });
}
} }
}; };
@ -118,7 +112,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
); );
if (imageError) { if (imageError) {
notify({ notify({
text: imageError.message text: intl.formatMessage(commonMessages.somethingWentWrong)
}); });
} }
}; };
@ -339,12 +333,10 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
defaultPrice={maybe(() => defaultPrice={maybe(() =>
data.product.basePrice.amount.toFixed(2) data.product.basePrice.amount.toFixed(2)
)} )}
errors={maybe( errors={
() => bulkProductVariantCreate.opts.data
bulkProductVariantCreate.opts.data ?.productVariantBulkCreate.errors || []
.productVariantBulkCreate.bulkProductErrors, }
[]
)}
open={params.action === "create-variants"} open={params.action === "create-variants"}
attributes={maybe( attributes={maybe(
() => data.product.productType.variantAttributes, () => data.product.productType.variantAttributes,

View file

@ -6,6 +6,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import { commonMessages } from "@saleor/intl";
import { decimal, maybe } from "../../misc"; import { decimal, maybe } from "../../misc";
import ProductVariantCreatePage, { import ProductVariantCreatePage, {
ProductVariantCreatePageSubmitData ProductVariantCreatePageSubmitData
@ -35,11 +36,9 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({ productId }) => {
} }
const handleCreateSuccess = (data: VariantCreate) => { const handleCreateSuccess = (data: VariantCreate) => {
if (data.productVariantCreate.productErrors.length === 0) { if (data.productVariantCreate.errors.length === 0) {
notify({ notify({
text: intl.formatMessage({ text: intl.formatMessage(commonMessages.savedChanges)
defaultMessage: "Product created"
})
}); });
navigate( navigate(
productVariantEditUrl( productVariantEditUrl(
@ -90,18 +89,16 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({ productId }) => {
/> />
<ProductVariantCreatePage <ProductVariantCreatePage
currencySymbol={maybe(() => shop.defaultCurrency)} currencySymbol={maybe(() => shop.defaultCurrency)}
errors={maybe( errors={
() => variantCreateResult.data?.productVariantCreate.errors ||
variantCreateResult.data.productVariantCreate
.productErrors,
[] []
)} }
header={intl.formatMessage({ header={intl.formatMessage({
defaultMessage: "Create Variant", defaultMessage: "Create Variant",
description: "header" description: "header"
})} })}
loading={disableForm} loading={disableForm}
product={maybe(() => data.product)} product={data?.product}
onBack={handleBack} onBack={handleBack}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onVariantClick={handleVariantClick} onVariantClick={handleVariantClick}

View file

@ -0,0 +1,65 @@
import { IntlShape, defineMessages } from "react-intl";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { ProductErrorCode } from "@saleor/types/globalTypes";
import { commonMessages } from "@saleor/intl";
import { BulkProductErrorFragment } from "@saleor/products/types/BulkProductErrorFragment";
import commonErrorMessages from "./common";
const messages = defineMessages({
attributeAlreadyAssigned: {
defaultMessage:
"This attribute has already been assigned to this product type"
},
attributeCannotBeAssigned: {
defaultMessage: "This attribute cannot be assigned to this product type"
},
attributeVariantsDisabled: {
defaultMessage: "Variants are disabled in this product type"
},
skuUnique: {
defaultMessage: "SKUs must be unique",
description: "bulk variant create error"
},
variantNoDigitalContent: {
defaultMessage: "This variant does not have any digital content"
}
});
function getProductErrorMessage(
err: Omit<ProductErrorFragment, "__typename"> | undefined,
intl: IntlShape
): string {
if (err) {
switch (err.code) {
case ProductErrorCode.ATTRIBUTE_ALREADY_ASSIGNED:
return intl.formatMessage(messages.attributeAlreadyAssigned);
case ProductErrorCode.ATTRIBUTE_CANNOT_BE_ASSIGNED:
return intl.formatMessage(messages.attributeCannotBeAssigned);
case ProductErrorCode.ATTRIBUTE_VARIANTS_DISABLED:
return intl.formatMessage(messages.attributeVariantsDisabled);
case ProductErrorCode.GRAPHQL_ERROR:
return intl.formatMessage(commonErrorMessages.graphqlError);
case ProductErrorCode.REQUIRED:
return intl.formatMessage(commonMessages.requiredField);
case ProductErrorCode.VARIANT_NO_DIGITAL_CONTENT:
return intl.formatMessage(messages.variantNoDigitalContent);
default:
return intl.formatMessage(commonErrorMessages.unknownError);
}
}
return undefined;
}
export function getBulkProductErrorMessage(
err: BulkProductErrorFragment | undefined,
intl: IntlShape
): string {
if (err?.code === ProductErrorCode.UNIQUE && err.field === "sku") {
return intl.formatMessage(messages.skuUnique);
}
return getProductErrorMessage(err, intl);
}
export default getProductErrorMessage;