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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 */
// 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
// ====================================================
export interface ProductCreate_productCreate_errors {
__typename: "Error";
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
message: string | null;
}
export interface ProductCreate_productCreate_product_category {

View file

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

View file

@ -2,16 +2,16 @@
/* eslint-disable */
// 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
// ====================================================
export interface ProductImageCreate_productImageCreate_errors {
__typename: "Error";
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
message: string | null;
}
export interface ProductImageCreate_productImageCreate_product_category {

View file

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

View file

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

View file

@ -2,16 +2,16 @@
/* eslint-disable */
// 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
// ====================================================
export interface ProductImageUpdate_productImageUpdate_errors {
__typename: "Error";
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
message: string | null;
}
export interface ProductImageUpdate_productImageUpdate_product_category {

View file

@ -2,16 +2,16 @@
/* eslint-disable */
// 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
// ====================================================
export interface ProductUpdate_productUpdate_errors {
__typename: "Error";
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
message: string | null;
}
export interface ProductUpdate_productUpdate_product_category {

View file

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

View file

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

View file

@ -2,16 +2,16 @@
/* eslint-disable */
// 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
// ====================================================
export interface SimpleProductUpdate_productUpdate_errors {
__typename: "Error";
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
message: string | null;
}
export interface SimpleProductUpdate_productUpdate_product_category {
@ -183,9 +183,9 @@ export interface SimpleProductUpdate_productUpdate {
}
export interface SimpleProductUpdate_productVariantUpdate_errors {
__typename: "Error";
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
message: string | null;
}
export interface SimpleProductUpdate_productVariantUpdate_productVariant_attributes_attribute_values {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -58,13 +58,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = () => {
})
});
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)}
disabled={productCreateOpts.loading}
errors={maybe(
() => productCreateOpts.data.productCreate.errors,
[]
)}
errors={productCreateOpts.data?.productCreate.errors || []}
fetchCategories={searchCategory}
fetchCollections={searchCollection}
fetchProductTypes={searchProductTypes}

View file

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

View file

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