Rebase fixes

This commit is contained in:
dominik-zeglen 2020-03-24 15:05:26 +01:00
parent 7b77d8b126
commit 381665409f
22 changed files with 271 additions and 188 deletions

View file

@ -10,16 +10,17 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import SingleSelectField from "@saleor/components/SingleSelectField"; import SingleSelectField from "@saleor/components/SingleSelectField";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { UserError } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { getFieldError } from "@saleor/utils/errors"; import { getProductErrorMessage, getFormErrors } from "@saleor/utils/errors";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { AttributePageFormData } from "../AttributePage"; import { AttributePageFormData } from "../AttributePage";
import { getAttributeSlugErrorMessage } from "../../errors";
export interface AttributeDetailsProps { export interface AttributeDetailsProps {
canChangeType: boolean; canChangeType: boolean;
data: AttributePageFormData; data: AttributePageFormData;
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -48,6 +49,8 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
} }
]; ];
const formErrors = getFormErrors(["name", "slug", "inputType"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -56,21 +59,21 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!getFieldError(errors, "name")} error={!!formErrors.name}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Default Label", defaultMessage: "Default Label",
description: "attribute's label" description: "attribute's label"
})} })}
name={"name" as keyof AttributePageFormData} name={"name" as keyof AttributePageFormData}
fullWidth fullWidth
helperText={getFieldError(errors, "name")?.message} helperText={getProductErrorMessage(formErrors.name, intl)}
value={data.name} value={data.name}
onChange={onChange} onChange={onChange}
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!getFieldError(errors, "slug")} error={!!formErrors.slug}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Attribute Code", defaultMessage: "Attribute Code",
description: "attribute's slug short code label" description: "attribute's slug short code label"
@ -79,7 +82,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
placeholder={slugify(data.name).toLowerCase()} placeholder={slugify(data.name).toLowerCase()}
fullWidth fullWidth
helperText={ helperText={
getFieldError(errors, "slug")?.message || getAttributeSlugErrorMessage(formErrors.slug, intl) ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: defaultMessage:
"This is used internally. Make sure you dont use spaces", "This is used internally. Make sure you dont use spaces",
@ -93,8 +96,8 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
<SingleSelectField <SingleSelectField
choices={inputTypeChoices} choices={inputTypeChoices}
disabled={disabled || !canChangeType} disabled={disabled || !canChangeType}
error={!!getFieldError(errors, "inputType")} error={!!formErrors.inputType}
hint={getFieldError(errors, "inputType")?.message} hint={getProductErrorMessage(formErrors.inputType, intl)}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Catalog Input type for Store Owner", defaultMessage: "Catalog Input type for Store Owner",
description: "attribute's editor component" description: "attribute's editor component"

View file

@ -12,8 +12,9 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ReorderAction, UserError } from "@saleor/types"; import { ReorderAction } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { import {
AttributeDetailsFragment, AttributeDetailsFragment,
AttributeDetailsFragment_values AttributeDetailsFragment_values
@ -25,7 +26,7 @@ import AttributeValues from "../AttributeValues";
export interface AttributePageProps { export interface AttributePageProps {
attribute: AttributeDetailsFragment | null; attribute: AttributeDetailsFragment | null;
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
values: AttributeDetailsFragment_values[]; values: AttributeDetailsFragment_values[];
onBack: () => void; onBack: () => void;

View file

@ -11,14 +11,14 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
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";
import { AttributePageFormData } from "../AttributePage"; import { AttributePageFormData } from "../AttributePage";
export interface AttributePropertiesProps { export interface AttributePropertiesProps {
data: AttributePageFormData; data: AttributePageFormData;
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -30,6 +30,8 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["storefrontSearchPosition"], errors);
return ( return (
<Card> <Card>
<CardTitle title={intl.formatMessage(commonMessages.properties)} /> <CardTitle title={intl.formatMessage(commonMessages.properties)} />
@ -86,11 +88,12 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
{data.filterableInStorefront && ( {data.filterableInStorefront && (
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!getFieldError(errors, "storefrontSearchPosition")} error={!!formErrors.storefrontSearchPosition}
fullWidth fullWidth
helperText={ helperText={getProductErrorMessage(
getFieldError(errors, "storefrontSearchPosition")?.message formErrors.storefrontSearchPosition,
} intl
)}
name={"storefrontSearchPosition" as keyof AttributePageFormData} name={"storefrontSearchPosition" as keyof AttributePageFormData}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Position in faceted navigation", defaultMessage: "Position in faceted navigation",

View file

@ -14,8 +14,9 @@ import Form from "@saleor/components/Form";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import { buttonMessages } from "@saleor/intl"; import { buttonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { UserError } from "@saleor/types"; import { getFormErrors } from "@saleor/utils/errors";
import { getFieldError } from "@saleor/utils/errors"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { getAttributeValueErrorMessage } from "@saleor/attributes/errors";
import { AttributeDetails_attribute_values } from "../../types/AttributeDetails"; import { AttributeDetails_attribute_values } from "../../types/AttributeDetails";
export interface AttributeValueEditDialogFormData { export interface AttributeValueEditDialogFormData {
@ -25,7 +26,7 @@ export interface AttributeValueEditDialogProps {
attributeValue: AttributeDetails_attribute_values | null; attributeValue: AttributeDetails_attribute_values | null;
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: ProductErrorFragment[];
open: boolean; open: boolean;
onSubmit: (data: AttributeValueEditDialogFormData) => void; onSubmit: (data: AttributeValueEditDialogFormData) => void;
onClose: () => void; onClose: () => void;
@ -45,6 +46,7 @@ const AttributeValueEditDialog: React.FC<AttributeValueEditDialogProps> = ({
name: maybe(() => attributeValue.name, "") name: maybe(() => attributeValue.name, "")
}; };
const errors = useModalDialogErrors(apiErrors, open); const errors = useModalDialogErrors(apiErrors, open);
const formErrors = getFormErrors(["name"], errors);
return ( return (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
@ -68,9 +70,12 @@ const AttributeValueEditDialog: React.FC<AttributeValueEditDialogProps> = ({
<TextField <TextField
autoFocus autoFocus
disabled={disabled} disabled={disabled}
error={!!getFieldError(errors, "name")} error={!!formErrors.name}
fullWidth fullWidth
helperText={getFieldError(errors, "name")?.message} helperText={getAttributeValueErrorMessage(
formErrors.name,
intl
)}
name={"name" as keyof AttributeValueEditDialogFormData} name={"name" as keyof AttributeValueEditDialogFormData}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Name", defaultMessage: "Name",

38
src/attributes/errors.ts Normal file
View file

@ -0,0 +1,38 @@
import { IntlShape, defineMessages } from "react-intl";
import { ProductErrorCode } from "@saleor/types/globalTypes";
import { getProductErrorMessage } from "@saleor/utils/errors";
import { ProductErrorFragment } from "./types/ProductErrorFragment";
const messages = defineMessages({
attributeSlugUnique: {
defaultMessage: "Attribute with this slug already exists"
},
attributeValueAlreadyExists: {
defaultMessage: "This value already exists within this attribute"
}
});
export function getAttributeSlugErrorMessage(
err: ProductErrorFragment,
intl: IntlShape
): string {
switch (err?.code) {
case ProductErrorCode.UNIQUE:
return intl.formatMessage(messages.attributeSlugUnique);
default:
return getProductErrorMessage(err, intl);
}
}
export function getAttributeValueErrorMessage(
err: ProductErrorFragment,
intl: IntlShape
): string {
switch (err?.code) {
case ProductErrorCode.ALREADY_EXISTS:
return intl.formatMessage(messages.attributeValueAlreadyExists);
default:
return getProductErrorMessage(err, intl);
}
}

View file

@ -35,12 +35,19 @@ import {
AttributeValueUpdateVariables AttributeValueUpdateVariables
} from "./types/AttributeValueUpdate"; } from "./types/AttributeValueUpdate";
export const productErrorFragment = gql`
fragment ProductErrorFragment on ProductError {
code
field
}
`;
const attributeBulkDelete = gql` const attributeBulkDelete = gql`
${productErrorFragment}
mutation AttributeBulkDelete($ids: [ID!]!) { mutation AttributeBulkDelete($ids: [ID!]!) {
attributeBulkDelete(ids: $ids) { attributeBulkDelete(ids: $ids) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
} }
} }
@ -51,11 +58,11 @@ export const AttributeBulkDeleteMutation = TypedMutation<
>(attributeBulkDelete); >(attributeBulkDelete);
const attributeDelete = gql` const attributeDelete = gql`
${productErrorFragment}
mutation AttributeDelete($id: ID!) { mutation AttributeDelete($id: ID!) {
attributeDelete(id: $id) { attributeDelete(id: $id) {
errors { errors: productErrors {
field ...ProductErrorFragment
message
} }
} }
} }
@ -67,15 +74,15 @@ export const AttributeDeleteMutation = TypedMutation<
export const attributeUpdateMutation = gql` export const attributeUpdateMutation = gql`
${attributeDetailsFragment} ${attributeDetailsFragment}
${productErrorFragment}
mutation AttributeUpdate($id: ID!, $input: AttributeUpdateInput!) { mutation AttributeUpdate($id: ID!, $input: AttributeUpdateInput!) {
attributeUpdate(id: $id, input: $input) { attributeUpdate(id: $id, input: $input) {
errors {
field
message
}
attribute { attribute {
...AttributeDetailsFragment ...AttributeDetailsFragment
} }
errors: productErrors {
...ProductErrorFragment
}
} }
} }
`; `;
@ -86,15 +93,15 @@ export const AttributeUpdateMutation = TypedMutation<
const attributeValueDelete = gql` const attributeValueDelete = gql`
${attributeDetailsFragment} ${attributeDetailsFragment}
${productErrorFragment}
mutation AttributeValueDelete($id: ID!) { mutation AttributeValueDelete($id: ID!) {
attributeValueDelete(id: $id) { attributeValueDelete(id: $id) {
errors {
field
message
}
attribute { attribute {
...AttributeDetailsFragment ...AttributeDetailsFragment
} }
errors: productErrors {
...ProductErrorFragment
}
} }
} }
`; `;
@ -105,15 +112,15 @@ export const AttributeValueDeleteMutation = TypedMutation<
export const attributeValueUpdateMutation = gql` export const attributeValueUpdateMutation = gql`
${attributeDetailsFragment} ${attributeDetailsFragment}
${productErrorFragment}
mutation AttributeValueUpdate($id: ID!, $input: AttributeValueCreateInput!) { mutation AttributeValueUpdate($id: ID!, $input: AttributeValueCreateInput!) {
attributeValueUpdate(id: $id, input: $input) { attributeValueUpdate(id: $id, input: $input) {
errors {
field
message
}
attribute { attribute {
...AttributeDetailsFragment ...AttributeDetailsFragment
} }
errors: productErrors {
...ProductErrorFragment
}
} }
} }
`; `;
@ -124,15 +131,15 @@ export const AttributeValueUpdateMutation = TypedMutation<
export const attributeValueCreateMutation = gql` export const attributeValueCreateMutation = gql`
${attributeDetailsFragment} ${attributeDetailsFragment}
${productErrorFragment}
mutation AttributeValueCreate($id: ID!, $input: AttributeValueCreateInput!) { mutation AttributeValueCreate($id: ID!, $input: AttributeValueCreateInput!) {
attributeValueCreate(attribute: $id, input: $input) { attributeValueCreate(attribute: $id, input: $input) {
errors {
field
message
}
attribute { attribute {
...AttributeDetailsFragment ...AttributeDetailsFragment
} }
errors: productErrors {
...ProductErrorFragment
}
} }
} }
`; `;
@ -143,15 +150,15 @@ export const AttributeValueCreateMutation = TypedMutation<
export const attributeCreateMutation = gql` export const attributeCreateMutation = gql`
${attributeDetailsFragment} ${attributeDetailsFragment}
${productErrorFragment}
mutation AttributeCreate($input: AttributeCreateInput!) { mutation AttributeCreate($input: AttributeCreateInput!) {
attributeCreate(input: $input) { attributeCreate(input: $input) {
errors {
field
message
}
attribute { attribute {
...AttributeDetailsFragment ...AttributeDetailsFragment
} }
errors: productErrors {
...ProductErrorFragment
}
} }
} }
`; `;
@ -161,18 +168,18 @@ export const AttributeCreateMutation = TypedMutation<
>(attributeCreateMutation); >(attributeCreateMutation);
const attributeValueReorderMutation = gql` const attributeValueReorderMutation = gql`
${productErrorFragment}
mutation AttributeValueReorder($id: ID!, $move: ReorderInput!) { mutation AttributeValueReorder($id: ID!, $move: ReorderInput!) {
attributeReorderValues(attributeId: $id, moves: [$move]) { attributeReorderValues(attributeId: $id, moves: [$move]) {
errors {
field
message
}
attribute { attribute {
id id
values { values {
id id
} }
} }
errors: productErrors {
...ProductErrorFragment
}
} }
} }
`; `;

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: AttributeBulkDelete // GraphQL mutation operation: AttributeBulkDelete
// ==================================================== // ====================================================
export interface AttributeBulkDelete_attributeBulkDelete_errors { export interface AttributeBulkDelete_attributeBulkDelete_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface AttributeBulkDelete_attributeBulkDelete { export interface AttributeBulkDelete_attributeBulkDelete {

View file

@ -2,18 +2,12 @@
/* 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 { AttributeCreateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; import { AttributeCreateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: AttributeCreate // GraphQL mutation operation: AttributeCreate
// ==================================================== // ====================================================
export interface AttributeCreate_attributeCreate_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface AttributeCreate_attributeCreate_attribute_values { export interface AttributeCreate_attributeCreate_attribute_values {
__typename: "AttributeValue"; __typename: "AttributeValue";
id: string; id: string;
@ -37,10 +31,16 @@ export interface AttributeCreate_attributeCreate_attribute {
values: (AttributeCreate_attributeCreate_attribute_values | null)[] | null; values: (AttributeCreate_attributeCreate_attribute_values | null)[] | null;
} }
export interface AttributeCreate_attributeCreate_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface AttributeCreate_attributeCreate { export interface AttributeCreate_attributeCreate {
__typename: "AttributeCreate"; __typename: "AttributeCreate";
errors: AttributeCreate_attributeCreate_errors[];
attribute: AttributeCreate_attributeCreate_attribute | null; attribute: AttributeCreate_attributeCreate_attribute | null;
errors: AttributeCreate_attributeCreate_errors[];
} }
export interface AttributeCreate { export interface AttributeCreate {

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: AttributeDelete // GraphQL mutation operation: AttributeDelete
// ==================================================== // ====================================================
export interface AttributeDelete_attributeDelete_errors { export interface AttributeDelete_attributeDelete_errors {
__typename: "Error"; __typename: "ProductError";
code: ProductErrorCode;
field: string | null; field: string | null;
message: string | null;
} }
export interface AttributeDelete_attributeDelete { export interface AttributeDelete_attributeDelete {

View file

@ -2,18 +2,12 @@
/* 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 { AttributeUpdateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; import { AttributeUpdateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: AttributeUpdate // GraphQL mutation operation: AttributeUpdate
// ==================================================== // ====================================================
export interface AttributeUpdate_attributeUpdate_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface AttributeUpdate_attributeUpdate_attribute_values { export interface AttributeUpdate_attributeUpdate_attribute_values {
__typename: "AttributeValue"; __typename: "AttributeValue";
id: string; id: string;
@ -37,10 +31,16 @@ export interface AttributeUpdate_attributeUpdate_attribute {
values: (AttributeUpdate_attributeUpdate_attribute_values | null)[] | null; values: (AttributeUpdate_attributeUpdate_attribute_values | null)[] | null;
} }
export interface AttributeUpdate_attributeUpdate_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface AttributeUpdate_attributeUpdate { export interface AttributeUpdate_attributeUpdate {
__typename: "AttributeUpdate"; __typename: "AttributeUpdate";
errors: AttributeUpdate_attributeUpdate_errors[];
attribute: AttributeUpdate_attributeUpdate_attribute | null; attribute: AttributeUpdate_attributeUpdate_attribute | null;
errors: AttributeUpdate_attributeUpdate_errors[];
} }
export interface AttributeUpdate { export interface AttributeUpdate {

View file

@ -2,18 +2,12 @@
/* 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 { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: AttributeValueCreate // GraphQL mutation operation: AttributeValueCreate
// ==================================================== // ====================================================
export interface AttributeValueCreate_attributeValueCreate_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface AttributeValueCreate_attributeValueCreate_attribute_values { export interface AttributeValueCreate_attributeValueCreate_attribute_values {
__typename: "AttributeValue"; __typename: "AttributeValue";
id: string; id: string;
@ -37,10 +31,16 @@ export interface AttributeValueCreate_attributeValueCreate_attribute {
values: (AttributeValueCreate_attributeValueCreate_attribute_values | null)[] | null; values: (AttributeValueCreate_attributeValueCreate_attribute_values | null)[] | null;
} }
export interface AttributeValueCreate_attributeValueCreate_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface AttributeValueCreate_attributeValueCreate { export interface AttributeValueCreate_attributeValueCreate {
__typename: "AttributeValueCreate"; __typename: "AttributeValueCreate";
errors: AttributeValueCreate_attributeValueCreate_errors[];
attribute: AttributeValueCreate_attributeValueCreate_attribute | null; attribute: AttributeValueCreate_attributeValueCreate_attribute | null;
errors: AttributeValueCreate_attributeValueCreate_errors[];
} }
export interface AttributeValueCreate { export interface AttributeValueCreate {

View file

@ -2,18 +2,12 @@
/* 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, AttributeValueType } from "./../../types/globalTypes"; import { AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: AttributeValueDelete // GraphQL mutation operation: AttributeValueDelete
// ==================================================== // ====================================================
export interface AttributeValueDelete_attributeValueDelete_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface AttributeValueDelete_attributeValueDelete_attribute_values { export interface AttributeValueDelete_attributeValueDelete_attribute_values {
__typename: "AttributeValue"; __typename: "AttributeValue";
id: string; id: string;
@ -37,10 +31,16 @@ export interface AttributeValueDelete_attributeValueDelete_attribute {
values: (AttributeValueDelete_attributeValueDelete_attribute_values | null)[] | null; values: (AttributeValueDelete_attributeValueDelete_attribute_values | null)[] | null;
} }
export interface AttributeValueDelete_attributeValueDelete_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface AttributeValueDelete_attributeValueDelete { export interface AttributeValueDelete_attributeValueDelete {
__typename: "AttributeValueDelete"; __typename: "AttributeValueDelete";
errors: AttributeValueDelete_attributeValueDelete_errors[];
attribute: AttributeValueDelete_attributeValueDelete_attribute | null; attribute: AttributeValueDelete_attributeValueDelete_attribute | null;
errors: AttributeValueDelete_attributeValueDelete_errors[];
} }
export interface AttributeValueDelete { export interface AttributeValueDelete {

View file

@ -2,18 +2,12 @@
/* 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 { ReorderInput } from "./../../types/globalTypes"; import { ReorderInput, ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: AttributeValueReorder // GraphQL mutation operation: AttributeValueReorder
// ==================================================== // ====================================================
export interface AttributeValueReorder_attributeReorderValues_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface AttributeValueReorder_attributeReorderValues_attribute_values { export interface AttributeValueReorder_attributeReorderValues_attribute_values {
__typename: "AttributeValue"; __typename: "AttributeValue";
id: string; id: string;
@ -25,10 +19,16 @@ export interface AttributeValueReorder_attributeReorderValues_attribute {
values: (AttributeValueReorder_attributeReorderValues_attribute_values | null)[] | null; values: (AttributeValueReorder_attributeReorderValues_attribute_values | null)[] | null;
} }
export interface AttributeValueReorder_attributeReorderValues_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface AttributeValueReorder_attributeReorderValues { export interface AttributeValueReorder_attributeReorderValues {
__typename: "AttributeReorderValues"; __typename: "AttributeReorderValues";
errors: AttributeValueReorder_attributeReorderValues_errors[];
attribute: AttributeValueReorder_attributeReorderValues_attribute | null; attribute: AttributeValueReorder_attributeReorderValues_attribute | null;
errors: AttributeValueReorder_attributeReorderValues_errors[];
} }
export interface AttributeValueReorder { export interface AttributeValueReorder {

View file

@ -2,18 +2,12 @@
/* 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 { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType } from "./../../types/globalTypes"; import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: AttributeValueUpdate // GraphQL mutation operation: AttributeValueUpdate
// ==================================================== // ====================================================
export interface AttributeValueUpdate_attributeValueUpdate_errors {
__typename: "Error";
field: string | null;
message: string | null;
}
export interface AttributeValueUpdate_attributeValueUpdate_attribute_values { export interface AttributeValueUpdate_attributeValueUpdate_attribute_values {
__typename: "AttributeValue"; __typename: "AttributeValue";
id: string; id: string;
@ -37,10 +31,16 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute {
values: (AttributeValueUpdate_attributeValueUpdate_attribute_values | null)[] | null; values: (AttributeValueUpdate_attributeValueUpdate_attribute_values | null)[] | null;
} }
export interface AttributeValueUpdate_attributeValueUpdate_errors {
__typename: "ProductError";
code: ProductErrorCode;
field: string | null;
}
export interface AttributeValueUpdate_attributeValueUpdate { export interface AttributeValueUpdate_attributeValueUpdate {
__typename: "AttributeValueUpdate"; __typename: "AttributeValueUpdate";
errors: AttributeValueUpdate_attributeValueUpdate_errors[];
attribute: AttributeValueUpdate_attributeValueUpdate_attribute | null; attribute: AttributeValueUpdate_attributeValueUpdate_attribute | null;
errors: AttributeValueUpdate_attributeValueUpdate_errors[];
} }
export interface AttributeValueUpdate { export interface AttributeValueUpdate {

View file

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

View file

@ -5,7 +5,7 @@ import slugify from "slugify";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ReorderEvent, UserError } from "@saleor/types"; import { ReorderEvent } from "@saleor/types";
import { import {
add, add,
isSelected, isSelected,
@ -14,6 +14,8 @@ import {
updateAtIndex updateAtIndex
} from "@saleor/utils/lists"; } from "@saleor/utils/lists";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { ProductErrorCode } from "@saleor/types/globalTypes";
import AttributePage from "../../components/AttributePage"; import AttributePage from "../../components/AttributePage";
import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog"; import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog";
import AttributeValueEditDialog, { import AttributeValueEditDialog, {
@ -33,6 +35,12 @@ interface AttributeDetailsProps {
params: AttributeAddUrlQueryParams; params: AttributeAddUrlQueryParams;
} }
const attributeValueAlreadyExistsError: ProductErrorFragment = {
__typename: "ProductError",
code: ProductErrorCode.ALREADY_EXISTS,
field: "name"
};
function areValuesEqual( function areValuesEqual(
a: AttributeValueEditDialogFormData, a: AttributeValueEditDialogFormData,
b: AttributeValueEditDialogFormData b: AttributeValueEditDialogFormData
@ -48,7 +56,9 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
const [values, setValues] = React.useState< const [values, setValues] = React.useState<
AttributeValueEditDialogFormData[] AttributeValueEditDialogFormData[]
>([]); >([]);
const [valueErrors, setValueErrors] = React.useState<UserError[]>([]); const [valueErrors, setValueErrors] = React.useState<ProductErrorFragment[]>(
[]
);
const id = params.id ? parseInt(params.id, 0) : undefined; const id = params.id ? parseInt(params.id, 0) : undefined;
@ -57,6 +67,8 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
AttributeAddUrlQueryParams AttributeAddUrlQueryParams
>(navigate, attributeAddUrl, params); >(navigate, attributeAddUrl, params);
React.useEffect(() => setValueErrors([]), [params.action]);
const handleValueDelete = () => { const handleValueDelete = () => {
setValues(remove(values[params.id], values, areValuesEqual)); setValues(remove(values[params.id], values, areValuesEqual));
closeModal(); closeModal();
@ -73,20 +85,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
}; };
const handleValueUpdate = (input: AttributeValueEditDialogFormData) => { const handleValueUpdate = (input: AttributeValueEditDialogFormData) => {
if (isSelected(input, values, areValuesEqual)) { if (isSelected(input, values, areValuesEqual)) {
setValueErrors([ setValueErrors([attributeValueAlreadyExistsError]);
{
field: "name",
message: intl.formatMessage(
{
defaultMessage: "A value named {name} already exists",
description: "attribute value edit error"
},
{
name: input.name
}
)
}
]);
} else { } else {
setValues(updateAtIndex(input, values, id)); setValues(updateAtIndex(input, values, id));
closeModal(); closeModal();
@ -94,20 +93,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
}; };
const handleValueCreate = (input: AttributeValueEditDialogFormData) => { const handleValueCreate = (input: AttributeValueEditDialogFormData) => {
if (isSelected(input, values, areValuesEqual)) { if (isSelected(input, values, areValuesEqual)) {
setValueErrors([ setValueErrors([attributeValueAlreadyExistsError]);
{
field: "name",
message: intl.formatMessage(
{
defaultMessage: "A value named {name} already exists",
description: "attribute value edit error"
},
{
name: input.name
}
)
}
]);
} else { } else {
setValues(add(input, values)); setValues(add(input, values));
closeModal(); closeModal();
@ -123,10 +109,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
<AttributePage <AttributePage
attribute={null} attribute={null}
disabled={false} disabled={false}
errors={maybe( errors={attributeCreateOpts.data?.attributeCreate.errors || []}
() => attributeCreateOpts.data.attributeCreate.errors,
[]
)}
onBack={() => navigate(attributeListUrl())} onBack={() => navigate(attributeListUrl())}
onDelete={undefined} onDelete={undefined}
onSubmit={input => onSubmit={input =>

View file

@ -8,6 +8,7 @@ import { maybe } from "@saleor/misc";
import { ReorderEvent } from "@saleor/types"; import { ReorderEvent } from "@saleor/types";
import { move } from "@saleor/utils/lists"; import { move } from "@saleor/utils/lists";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import { getProductErrorMessage } from "@saleor/utils/errors";
import AttributeDeleteDialog from "../../components/AttributeDeleteDialog"; import AttributeDeleteDialog from "../../components/AttributeDeleteDialog";
import AttributePage from "../../components/AttributePage"; import AttributePage from "../../components/AttributePage";
import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog"; import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog";
@ -95,7 +96,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
const handleValueReorderMutation = (data: AttributeValueReorder) => { const handleValueReorderMutation = (data: AttributeValueReorder) => {
if (data.attributeReorderValues.errors.length !== 0) { if (data.attributeReorderValues.errors.length !== 0) {
notify({ notify({
text: data.attributeReorderValues.errors[0].message text: getProductErrorMessage(
data.attributeReorderValues.errors[0],
intl
)
}); });
} }
}; };
@ -155,12 +159,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
<AttributePage <AttributePage
attribute={maybe(() => data.attribute)} attribute={maybe(() => data.attribute)}
disabled={loading} disabled={loading}
errors={maybe( errors={
() => attributeUpdateOpts.data
attributeUpdateOpts.data ?.attributeUpdate.errors || []
.attributeUpdate.errors, }
[]
)}
onBack={() => onBack={() =>
navigate(attributeListUrl()) navigate(attributeListUrl())
} }
@ -253,12 +255,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
attributeValueCreateOpts.status attributeValueCreateOpts.status
} }
disabled={loading} disabled={loading}
errors={maybe( errors={
() => attributeValueCreateOpts.data
attributeValueCreateOpts.data ?.attributeValueCreate.errors || []
.attributeValueCreate.errors, }
[]
)}
open={params.action === "add-value"} open={params.action === "add-value"}
onClose={closeModal} onClose={closeModal}
onSubmit={input => onSubmit={input =>
@ -280,12 +280,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
attributeValueUpdateOpts.status attributeValueUpdateOpts.status
} }
disabled={loading} disabled={loading}
errors={maybe( errors={
() => attributeValueUpdateOpts.data
attributeValueUpdateOpts.data ?.attributeValueUpdate.errors || []
.attributeValueUpdate.errors, }
[]
)}
open={params.action === "edit-value"} open={params.action === "edit-value"}
onClose={closeModal} onClose={closeModal}
onSubmit={input => onSubmit={input =>

View file

@ -95927,11 +95927,6 @@ Ctrl + K"
</legend> </legend>
</fieldset> </fieldset>
</div> </div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id MuiFormHelperText-filled-id"
>
Invalid value
</p>
</div> </div>
</div> </div>
</div> </div>
@ -96164,7 +96159,7 @@ Ctrl + K"
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id" class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
> >
<label <label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id" class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id"
data-shrink="false" data-shrink="false"
> >
Collections Collections
@ -96172,11 +96167,11 @@ Ctrl + K"
<div <div
aria-autocomplete="list" aria-autocomplete="list"
aria-expanded="false" aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id" class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox" role="combobox"
> >
<input <input
aria-invalid="true" aria-invalid="false"
autocomplete="off" autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id" class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text" type="text"
@ -96215,9 +96210,9 @@ Ctrl + K"
</fieldset> </fieldset>
</div> </div>
<p <p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id" class="MuiFormHelperText-root-id MuiFormHelperText-contained-id"
> >
Invalid value *Optional. Adding product to collection helps users find it.
</p> </p>
</div> </div>
</div> </div>
@ -99501,6 +99496,11 @@ Ctrl + K"
</legend> </legend>
</fieldset> </fieldset>
</div> </div>
<p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id MuiFormHelperText-filled-id"
>
Invalid value
</p>
</div> </div>
</div> </div>
</div> </div>
@ -99867,7 +99867,7 @@ Ctrl + K"
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id" class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
> >
<label <label
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id" class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id"
data-shrink="false" data-shrink="false"
> >
Collections Collections
@ -99875,11 +99875,11 @@ Ctrl + K"
<div <div
aria-autocomplete="list" aria-autocomplete="list"
aria-expanded="false" aria-expanded="false"
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id" class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id MuiInputBase-adornedEnd-id MuiOutlinedInput-adornedEnd-id"
role="combobox" role="combobox"
> >
<input <input
aria-invalid="false" aria-invalid="true"
autocomplete="off" autocomplete="off"
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id" class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
type="text" type="text"
@ -99918,9 +99918,9 @@ Ctrl + K"
</fieldset> </fieldset>
</div> </div>
<p <p
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id" class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id"
> >
*Optional. Adding product to collection helps users find it. Invalid value
</p> </p>
</div> </div>
</div> </div>

View file

@ -2,8 +2,10 @@ import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import { attribute } from "@saleor/attributes/fixtures"; import { attribute } from "@saleor/attributes/fixtures";
import { formError } from "@saleor/storybook/misc"; import {
import { AttributeValueType } from "@saleor/types/globalTypes"; AttributeValueType,
ProductErrorCode
} from "@saleor/types/globalTypes";
import AttributeValueEditDialog, { import AttributeValueEditDialog, {
AttributeValueEditDialogProps AttributeValueEditDialogProps
} from "../../../attributes/components/AttributeValueEditDialog"; } from "../../../attributes/components/AttributeValueEditDialog";
@ -26,5 +28,14 @@ storiesOf("Attributes / Attribute value edit", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <AttributeValueEditDialog {...props} />) .add("default", () => <AttributeValueEditDialog {...props} />)
.add("form errors", () => ( .add("form errors", () => (
<AttributeValueEditDialog {...props} errors={[formError("name")]} /> <AttributeValueEditDialog
{...props}
errors={[
{
__typename: "ProductError",
code: ProductErrorCode.INVALID,
field: "name"
}
]}
/>
)); ));

View file

@ -7,7 +7,7 @@ import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelec
export interface UserError { export interface UserError {
field: string | null; field: string | null;
message: string; message?: string;
} }
export interface DialogProps { export interface DialogProps {

View file

@ -1,11 +0,0 @@
import { UserError } from "@saleor/types";
export function getFieldError(errors: UserError[], field: string): UserError {
return errors.find(err => err.field === field);
}
export function getErrors(errors: UserError[]): string[] {
return errors
.filter(err => ["", null].includes(err.field))
.map(err => err.message);
}

26
src/utils/errors/index.ts Normal file
View file

@ -0,0 +1,26 @@
import { UserError } from "@saleor/types";
export function getFieldError<T extends UserError>(
errors: T[],
field: string
): T {
return errors.find(err => err.field === field);
}
export function getErrors(errors: UserError[]): string[] {
return errors
.filter(err => ["", null].includes(err.field))
.map(err => err.message);
}
export function getFormErrors<TField extends string, TError extends UserError>(
fields: TField[],
errors: TError[]
): Record<TField, TError> {
return fields.reduce((errs, field) => {
errs[field] = getFieldError(errors, field);
return errs;
}, ({} as unknown) as Record<TField, TError>);
}
export { default as getProductErrorMessage } from "./product";