Merge pull request #410 from mirumee/ref/do-not-format-errors-in-forms

Do not store errors in form component
This commit is contained in:
Marcin Gębala 2020-02-25 15:54:27 +01:00 committed by GitHub
commit 16291446a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 2242 additions and 778 deletions

View file

@ -35,6 +35,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Use structurized JSON files instead of PO - #403 by @dominik-zeglen - Use structurized JSON files instead of PO - #403 by @dominik-zeglen
- Remove PO files from repo and update translations #409 by @dominik-zeglen - Remove PO files from repo and update translations #409 by @dominik-zeglen
- Add optional chaining and explicitely return "Not found" page - #408 by @dominik-zeglen - Add optional chaining and explicitely return "Not found" page - #408 by @dominik-zeglen
- Do not store errors in form component - #410 by @dominik-zeglen
## 2.0.0 ## 2.0.0

View file

@ -10,15 +10,16 @@ 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 { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { getFieldError } from "@saleor/utils/errors";
import { AttributePageFormData } from "../AttributePage"; import { AttributePageFormData } from "../AttributePage";
export interface AttributeDetailsProps { export interface AttributeDetailsProps {
canChangeType: boolean; canChangeType: boolean;
data: AttributePageFormData; data: AttributePageFormData;
disabled: boolean; disabled: boolean;
errors: FormErrors<"name" | "slug" | "inputType">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -55,21 +56,21 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "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={errors.name} helperText={getFieldError(errors, "name")?.message}
value={data.name} value={data.name}
onChange={onChange} onChange={onChange}
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.slug} error={!!getFieldError(errors, "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"
@ -78,7 +79,7 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
placeholder={slugify(data.name).toLowerCase()} placeholder={slugify(data.name).toLowerCase()}
fullWidth fullWidth
helperText={ helperText={
errors.slug || getFieldError(errors, "slug")?.message ||
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",
@ -92,8 +93,8 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
<SingleSelectField <SingleSelectField
choices={inputTypeChoices} choices={inputTypeChoices}
disabled={disabled || !canChangeType} disabled={disabled || !canChangeType}
error={!!errors.inputType} error={!!getFieldError(errors, "inputType")}
hint={errors.inputType} hint={getFieldError(errors, "inputType")?.message}
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

@ -108,8 +108,8 @@ const AttributePage: React.FC<AttributePageProps> = ({
}); });
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={handleSubmit}> <Form initial={initialForm} onSubmit={handleSubmit}>
{({ change, errors: formErrors, data, submit }) => ( {({ change, data, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.attributes)} {intl.formatMessage(sectionNames.attributes)}
@ -130,7 +130,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
canChangeType={attribute === null} canChangeType={attribute === null}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -146,7 +146,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
<div> <div>
<AttributeProperties <AttributeProperties
data={data} data={data}
errors={formErrors} errors={errors}
disabled={disabled} disabled={disabled}
onChange={change} onChange={change}
/> />

View file

@ -11,13 +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 { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { AttributePageFormData } from "../AttributePage"; import { AttributePageFormData } from "../AttributePage";
export interface AttributePropertiesProps { export interface AttributePropertiesProps {
data: AttributePageFormData; data: AttributePageFormData;
disabled: boolean; disabled: boolean;
errors: FormErrors<"storefrontSearchPosition">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -85,9 +86,11 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
{data.filterableInStorefront && ( {data.filterableInStorefront && (
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.storefrontSearchPosition} error={!!getFieldError(errors, "storefrontSearchPosition")}
fullWidth fullWidth
helperText={errors.storefrontSearchPosition} helperText={
getFieldError(errors, "storefrontSearchPosition")?.message
}
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

@ -15,6 +15,7 @@ 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 { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { AttributeDetails_attribute_values } from "../../types/AttributeDetails"; import { AttributeDetails_attribute_values } from "../../types/AttributeDetails";
export interface AttributeValueEditDialogFormData { export interface AttributeValueEditDialogFormData {
@ -60,16 +61,16 @@ const AttributeValueEditDialog: React.FC<AttributeValueEditDialogProps> = ({
/> />
)} )}
</DialogTitle> </DialogTitle>
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, submit }) => ( {({ change, data, submit }) => (
<> <>
<DialogContent> <DialogContent>
<TextField <TextField
autoFocus autoFocus
disabled={disabled} disabled={disabled}
error={!!formErrors.name} error={!!getFieldError(errors, "name")}
fullWidth fullWidth
helperText={formErrors.name} helperText={getFieldError(errors, "name")?.message}
name={"name" as keyof AttributeValueEditDialogFormData} name={"name" as keyof AttributeValueEditDialogFormData}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Name", defaultMessage: "Name",

View file

@ -40,18 +40,13 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
disabled, disabled,
onSubmit, onSubmit,
onBack, onBack,
errors: userErrors, errors,
saveButtonBarState saveButtonBarState
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
return ( return (
<Form <Form onSubmit={onSubmit} initial={initialData} confirmLeave>
onSubmit={onSubmit} {({ data, change, submit, hasChanged }) => (
initial={initialData}
errors={userErrors}
confirmLeave
>
{({ data, change, errors, submit, hasChanged }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.categories)} {intl.formatMessage(sectionNames.categories)}

View file

@ -9,6 +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 { getFieldError } from "@saleor/utils/errors";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { CategoryDetails_category } from "../../types/CategoryDetails"; import { CategoryDetails_category } from "../../types/CategoryDetails";
@ -19,7 +21,7 @@ interface CategoryDetailsFormProps {
description: RawDraftContentState; description: RawDraftContentState;
}; };
disabled: boolean; disabled: boolean;
errors: { [key: string]: string }; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -47,16 +49,16 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
disabled={disabled} disabled={disabled}
value={data && data.name} value={data && data.name}
onChange={onChange} onChange={onChange}
error={!!errors.name} error={!!getFieldError(errors, "name")}
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
fullWidth fullWidth
/> />
</div> </div>
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
disabled={disabled} disabled={disabled}
error={!!errors.descriptionJson} error={!!getFieldError(errors, "descriptionJson")}
helperText={errors.descriptionJson} helperText={getFieldError(errors, "descriptionJson")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Category Description" defaultMessage: "Category Description"
})} })}

View file

@ -75,7 +75,7 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
currentTab, currentTab,
category, category,
disabled, disabled,
errors: userErrors, errors,
pageInfo, pageInfo,
products, products,
saveButtonBarState, saveButtonBarState,
@ -115,13 +115,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
seoTitle: "" seoTitle: ""
}; };
return ( return (
<Form <Form onSubmit={onSubmit} initial={initialData} confirmLeave>
onSubmit={onSubmit} {({ data, change, submit, hasChanged }) => (
initial={initialData}
errors={userErrors}
confirmLeave
>
{({ data, change, errors, submit, hasChanged }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.categories)} {intl.formatMessage(sectionNames.categories)}

View file

@ -68,8 +68,8 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.collections)} {intl.formatMessage(sectionNames.collections)}
@ -85,7 +85,7 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
<CollectionDetails <CollectionDetails
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -147,7 +147,7 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
<CardContent> <CardContent>
<VisibilityCard <VisibilityCard
data={data} data={data}
errors={formErrors} errors={errors}
disabled={disabled} disabled={disabled}
hiddenMessage={intl.formatMessage( hiddenMessage={intl.formatMessage(
{ {

View file

@ -10,7 +10,8 @@ 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 { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { CollectionDetails_collection } from "../../types/CollectionDetails"; import { CollectionDetails_collection } from "../../types/CollectionDetails";
export interface CollectionDetailsProps { export interface CollectionDetailsProps {
@ -20,7 +21,7 @@ export interface CollectionDetailsProps {
name: string; name: string;
}; };
disabled: boolean; disabled: boolean;
errors: FormErrors<"descriptionJson" | "name">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -48,14 +49,14 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
disabled={disabled} disabled={disabled}
value={data.name} value={data.name}
onChange={onChange} onChange={onChange}
error={!!errors.name} error={!!getFieldError(errors, "name")}
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
fullWidth fullWidth
/> />
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
error={!!errors.descriptionJson} error={!!getFieldError(errors, "descriptionJson")}
helperText={errors.descriptionJson} helperText={getFieldError(errors, "descriptionJson")?.message}
initial={maybe(() => JSON.parse(collection.descriptionJson))} initial={maybe(() => JSON.parse(collection.descriptionJson))}
label={intl.formatMessage(commonMessages.description)} label={intl.formatMessage(commonMessages.description)}
name="description" name="description"

View file

@ -18,7 +18,7 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ListActions, PageListProps } from "../../../types"; import { ListActions, PageListProps, UserError } from "../../../types";
import { CollectionDetails_collection } from "../../types/CollectionDetails"; import { CollectionDetails_collection } from "../../types/CollectionDetails";
import CollectionDetails from "../CollectionDetails/CollectionDetails"; import CollectionDetails from "../CollectionDetails/CollectionDetails";
import { CollectionImage } from "../CollectionImage/CollectionImage"; import { CollectionImage } from "../CollectionImage/CollectionImage";
@ -37,6 +37,7 @@ export interface CollectionDetailsPageFormData {
export interface CollectionDetailsPageProps extends PageListProps, ListActions { export interface CollectionDetailsPageProps extends PageListProps, ListActions {
collection: CollectionDetails_collection; collection: CollectionDetails_collection;
errors: UserError[];
isFeatured: boolean; isFeatured: boolean;
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void; onBack: () => void;
@ -50,6 +51,7 @@ export interface CollectionDetailsPageProps extends PageListProps, ListActions {
const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
collection, collection,
disabled, disabled,
errors,
isFeatured, isFeatured,
saveButtonBarState, saveButtonBarState,
onBack, onBack,
@ -77,7 +79,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
onSubmit={onSubmit} onSubmit={onSubmit}
confirmLeave confirmLeave
> >
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.collections)} {intl.formatMessage(sectionNames.collections)}
@ -89,7 +91,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
collection={collection} collection={collection}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -124,7 +126,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
<div> <div>
<VisibilityCard <VisibilityCard
data={data} data={data}
errors={formErrors} errors={errors}
disabled={disabled} disabled={disabled}
hiddenMessage={intl.formatMessage( hiddenMessage={intl.formatMessage(
{ {

View file

@ -205,6 +205,9 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
onBack={handleBack} onBack={handleBack}
disabled={loading} disabled={loading}
collection={maybe(() => data.collection)} collection={maybe(() => data.collection)}
errors={
updateCollection.opts?.data?.collectionUpdate.errors || []
}
isFeatured={maybe( isFeatured={maybe(
() => () =>
data.shop.homepageCollection.id === data.collection.id, data.shop.homepageCollection.id === data.collection.id,

View file

@ -5,7 +5,8 @@ import { useIntl } from "react-intl";
import { AddressTypeInput } from "@saleor/customers/types"; import { AddressTypeInput } from "@saleor/customers/types";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import FormSpacer from "../FormSpacer"; import FormSpacer from "../FormSpacer";
import SingleAutocompleteSelectField, { import SingleAutocompleteSelectField, {
SingleAutocompleteChoiceType SingleAutocompleteChoiceType
@ -27,7 +28,7 @@ interface AddressEditProps {
countryDisplayValue: string; countryDisplayValue: string;
data: AddressTypeInput; data: AddressTypeInput;
disabled?: boolean; disabled?: boolean;
errors: FormErrors<keyof AddressTypeInput>; errors: UserError[];
onChange(event: React.ChangeEvent<any>); onChange(event: React.ChangeEvent<any>);
onCountryChange(event: React.ChangeEvent<any>); onCountryChange(event: React.ChangeEvent<any>);
} }
@ -52,8 +53,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.firstName} error={!!getFieldError(errors, "firstName")}
helperText={errors.firstName} helperText={getFieldError(errors, "firstName")?.message}
label={intl.formatMessage(commonMessages.firstName)} label={intl.formatMessage(commonMessages.firstName)}
name="firstName" name="firstName"
onChange={onChange} onChange={onChange}
@ -64,8 +65,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.lastName} error={!!getFieldError(errors, "lastName")}
helperText={errors.lastName} helperText={getFieldError(errors, "lastName")?.message}
label={intl.formatMessage(commonMessages.lastName)} label={intl.formatMessage(commonMessages.lastName)}
name="lastName" name="lastName"
onChange={onChange} onChange={onChange}
@ -79,8 +80,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.companyName} error={!!getFieldError(errors, "companyName")}
helperText={errors.companyName} helperText={getFieldError(errors, "companyName")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Company" defaultMessage: "Company"
})} })}
@ -93,9 +94,9 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.phone} error={!!getFieldError(errors, "phone")}
fullWidth fullWidth
helperText={errors.phone} helperText={getFieldError(errors, "phone")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Phone" defaultMessage: "Phone"
})} })}
@ -108,8 +109,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.streetAddress1} error={!!getFieldError(errors, "streetAddress1")}
helperText={errors.streetAddress1} helperText={getFieldError(errors, "streetAddress1")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Address line 1" defaultMessage: "Address line 1"
})} })}
@ -121,8 +122,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.streetAddress2} error={!!getFieldError(errors, "streetAddress2")}
helperText={errors.streetAddress2} helperText={getFieldError(errors, "streetAddress2")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Address line 2" defaultMessage: "Address line 2"
})} })}
@ -136,8 +137,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.city} error={!!getFieldError(errors, "city")}
helperText={errors.city} helperText={getFieldError(errors, "city")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "City" defaultMessage: "City"
})} })}
@ -150,8 +151,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.postalCode} error={!!getFieldError(errors, "postalCode")}
helperText={errors.postalCode} helperText={getFieldError(errors, "postalCode")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "ZIP / Postal code" defaultMessage: "ZIP / Postal code"
})} })}
@ -169,8 +170,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
disabled={disabled} disabled={disabled}
displayValue={countryDisplayValue} displayValue={countryDisplayValue}
error={!!errors.country} error={!!getFieldError(errors, "country")}
helperText={errors.country} helperText={getFieldError(errors, "country")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Country" defaultMessage: "Country"
})} })}
@ -186,8 +187,8 @@ const AddressEdit: React.FC<AddressEditProps> = props => {
<div> <div>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.countryArea} error={!!getFieldError(errors, "countryArea")}
helperText={errors.countryArea} helperText={getFieldError(errors, "countryArea")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Country area" defaultMessage: "Country area"
})} })}

View file

@ -68,7 +68,7 @@ export const EditableTableCell: React.FC<EditableTableCellProps> = props => {
}; };
const [opened, setOpenStatus] = React.useState(focused); const [opened, setOpenStatus] = React.useState(focused);
const { change, data } = useForm({ value }, [], handleConfirm); const { change, data } = useForm({ value }, handleConfirm);
const enable = () => setOpenStatus(true); const enable = () => setOpenStatus(true);
const disable = () => setOpenStatus(false); const disable = () => setOpenStatus(false);

View file

@ -1,20 +1,18 @@
import React from "react"; import React from "react";
import useForm, { UseFormResult } from "@saleor/hooks/useForm"; import useForm, { UseFormResult } from "@saleor/hooks/useForm";
import { UserError } from "@saleor/types";
export interface FormProps<T> { export interface FormProps<T> {
children: (props: UseFormResult<T>) => React.ReactNode; children: (props: UseFormResult<T>) => React.ReactNode;
confirmLeave?: boolean; confirmLeave?: boolean;
errors?: UserError[];
initial?: T; initial?: T;
resetOnSubmit?: boolean; resetOnSubmit?: boolean;
onSubmit?: (data: T) => void; onSubmit?: (data: T) => void;
} }
function Form<T>(props: FormProps<T>) { function Form<T>(props: FormProps<T>) {
const { children, errors, initial, resetOnSubmit, onSubmit } = props; const { children, initial, resetOnSubmit, onSubmit } = props;
const renderProps = useForm(initial, errors, onSubmit); const renderProps = useForm(initial, onSubmit);
function handleSubmit(event?: React.FormEvent<any>, cb?: () => void) { function handleSubmit(event?: React.FormEvent<any>, cb?: () => void) {
const { reset, submit } = renderProps; const { reset, submit } = renderProps;

View file

@ -8,7 +8,8 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import RadioSwitchField from "@saleor/components/RadioSwitchField"; import RadioSwitchField from "@saleor/components/RadioSwitchField";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { DateContext } from "../Date/DateContext"; import { DateContext } from "../Date/DateContext";
import FormSpacer from "../FormSpacer"; import FormSpacer from "../FormSpacer";
@ -52,7 +53,7 @@ interface VisibilityCardProps {
isPublished: boolean; isPublished: boolean;
publicationDate: string; publicationDate: string;
}; };
errors: FormErrors<"isPublished" | "publicationDate">; errors: UserError[];
disabled?: boolean; disabled?: boolean;
hiddenMessage: string; hiddenMessage: string;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
@ -62,7 +63,6 @@ interface VisibilityCardProps {
export const VisibilityCard: React.FC<VisibilityCardProps> = props => { export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
const { const {
children, children,
data: { isPublished, publicationDate }, data: { isPublished, publicationDate },
errors, errors,
disabled, disabled,
@ -101,7 +101,7 @@ export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
<CardContent> <CardContent>
<RadioSwitchField <RadioSwitchField
disabled={disabled} disabled={disabled}
error={!!errors.isPublished} error={!!getFieldError(errors, "isPublished")}
firstOptionLabel={ firstOptionLabel={
<> <>
<p className={classes.label}> <p className={classes.label}>
@ -140,7 +140,7 @@ export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
)} )}
{isPublicationDate && ( {isPublicationDate && (
<TextField <TextField
error={!!errors.publicationDate} error={!!getFieldError(errors, "publicationDate")}
disabled={disabled} disabled={disabled}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Publish on", defaultMessage: "Publish on",
@ -149,7 +149,7 @@ export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
name="publicationDate" name="publicationDate"
type="date" type="date"
fullWidth={true} fullWidth={true}
helperText={errors.publicationDate} helperText={getFieldError(errors, "publicationDate")?.message}
value={publicationDate ? publicationDate : ""} value={publicationDate ? publicationDate : ""}
onChange={onChange} onChange={onChange}
className={classes.date} className={classes.date}
@ -160,10 +160,12 @@ export const VisibilityCard: React.FC<VisibilityCardProps> = props => {
)} )}
</> </>
)} )}
{errors.isPublished && ( {getFieldError(errors, "isPublished") && (
<> <>
<FormSpacer /> <FormSpacer />
<Typography color="error">{errors.isPublished}</Typography> <Typography color="error">
{getFieldError(errors, "isPublished")?.message}
</Typography>
</> </>
)} )}
<div className={classes.children}>{children}</div> <div className={classes.children}>{children}</div>

View file

@ -43,7 +43,10 @@ const styles = createStyles({
} }
}); });
const CustomerAddressDialog = withStyles(styles, {})( const CustomerAddressDialog = withStyles(
styles,
{}
)(
({ ({
address, address,
classes, classes,
@ -98,12 +101,8 @@ const CustomerAddressDialog = withStyles(styles, {})(
fullWidth fullWidth
maxWidth="sm" maxWidth="sm"
> >
<Form <Form initial={initialForm} onSubmit={handleSubmit}>
initial={initialForm} {({ change, data }) => {
errors={dialogErrors}
onSubmit={handleSubmit}
>
{({ change, data, errors: formErrors }) => {
const handleCountrySelect = createSingleAutocompleteSelectHandler( const handleCountrySelect = createSingleAutocompleteSelectHandler(
change, change,
setCountryDisplayName, setCountryDisplayName,
@ -130,7 +129,7 @@ const CustomerAddressDialog = withStyles(styles, {})(
countries={countryChoices} countries={countryChoices}
data={data} data={data}
countryDisplayValue={countryDisplayName} countryDisplayValue={countryDisplayName}
errors={formErrors} errors={dialogErrors}
onChange={change} onChange={change}
onCountryChange={handleCountrySelect} onCountryChange={handleCountrySelect}
/> />

View file

@ -9,7 +9,7 @@ import AddressEdit from "@saleor/components/AddressEdit";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import { FormErrors } from "../../../types"; import { UserError } from "../../../types";
import { AddressTypeInput } from "../../types"; import { AddressTypeInput } from "../../types";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -26,7 +26,7 @@ export interface CustomerCreateAddressProps {
countryDisplayName: string; countryDisplayName: string;
data: AddressTypeInput; data: AddressTypeInput;
disabled: boolean; disabled: boolean;
errors: FormErrors<keyof AddressTypeInput>; errors: UserError[];
onChange(event: React.ChangeEvent<any>); onChange(event: React.ChangeEvent<any>);
onCountryChange(event: React.ChangeEvent<any>); onCountryChange(event: React.ChangeEvent<any>);
} }

View file

@ -7,7 +7,8 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
import { CustomerCreatePageFormData } from "../CustomerCreatePage"; import { CustomerCreatePageFormData } from "../CustomerCreatePage";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -25,7 +26,7 @@ const useStyles = makeStyles(
export interface CustomerCreateDetailsProps { export interface CustomerCreateDetailsProps {
data: CustomerCreatePageFormData; data: CustomerCreatePageFormData;
disabled: boolean; disabled: boolean;
errors: FormErrors<"customerFirstName" | "customerLastName" | "email">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -47,33 +48,33 @@ const CustomerCreateDetails: React.FC<CustomerCreateDetailsProps> = props => {
<div className={classes.root}> <div className={classes.root}>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.customerFirstName} error={!!getFieldError(errors, "customerFirstName")}
fullWidth fullWidth
name="customerFirstName" name="customerFirstName"
label={intl.formatMessage(commonMessages.firstName)} label={intl.formatMessage(commonMessages.firstName)}
helperText={errors.customerFirstName} helperText={getFieldError(errors, "customerFirstName")?.message}
type="text" type="text"
value={data.customerFirstName} value={data.customerFirstName}
onChange={onChange} onChange={onChange}
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.customerLastName} error={!!getFieldError(errors, "customerLastName")}
fullWidth fullWidth
name="customerLastName" name="customerLastName"
label={intl.formatMessage(commonMessages.lastName)} label={intl.formatMessage(commonMessages.lastName)}
helperText={errors.customerLastName} helperText={getFieldError(errors, "customerLastName")?.message}
type="text" type="text"
value={data.customerLastName} value={data.customerLastName}
onChange={onChange} onChange={onChange}
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.email} error={!!getFieldError(errors, "email")}
fullWidth fullWidth
name="email" name="email"
label={intl.formatMessage(commonMessages.email)} label={intl.formatMessage(commonMessages.email)}
helperText={errors.email} helperText={getFieldError(errors, "email")?.message}
type="email" type="email"
value={data.email} value={data.email}
onChange={onChange} onChange={onChange}

View file

@ -7,15 +7,15 @@ import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
export interface CustomerCreateNoteProps { export interface CustomerCreateNoteProps {
data: { data: {
note: string; note: string;
}; };
disabled: boolean; disabled: boolean;
errors: Partial<{ errors: UserError[];
note: string;
}>;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -42,11 +42,11 @@ const CustomerCreateNote: React.FC<CustomerCreateNoteProps> = ({
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.note} error={!!getFieldError(errors, "note")}
fullWidth fullWidth
multiline multiline
name="note" name="note"
helperText={errors.note} helperText={getFieldError(errors, "note")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Note", defaultMessage: "Note",
description: "note about customer" description: "note about customer"

View file

@ -61,7 +61,7 @@ export interface CustomerCreatePageProps {
const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
countries, countries,
disabled, disabled,
errors, errors: apiErrors,
saveButtonBar, saveButtonBar,
onBack, onBack,
onSubmit onSubmit
@ -98,6 +98,8 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
}) })
); );
const errors = [...apiErrors, ...validationErrors];
const handleSubmit = ( const handleSubmit = (
formData: CustomerCreatePageFormData & AddressTypeInput formData: CustomerCreatePageFormData & AddressTypeInput
) => { ) => {
@ -130,13 +132,8 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
}; };
return ( return (
<Form <Form initial={initialForm} onSubmit={handleSubmit} confirmLeave>
initial={initialForm} {({ change, data, hasChanged, submit }) => {
onSubmit={handleSubmit}
errors={[...errors, ...validationErrors]}
confirmLeave
>
{({ change, data, errors: formErrors, hasChanged, submit }) => {
const handleCountrySelect = createSingleAutocompleteSelectHandler( const handleCountrySelect = createSingleAutocompleteSelectHandler(
change, change,
setCountryDisplayName, setCountryDisplayName,
@ -159,7 +156,7 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
<CustomerCreateDetails <CustomerCreateDetails
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -168,7 +165,7 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
countryDisplayName={countryDisplayName} countryDisplayName={countryDisplayName}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
onCountryChange={handleCountrySelect} onCountryChange={handleCountrySelect}
/> />
@ -176,7 +173,7 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
<CustomerCreateNote <CustomerCreateNote
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
</div> </div>

View file

@ -11,7 +11,8 @@ import CardTitle from "@saleor/components/CardTitle";
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { CustomerDetails_user } from "../../types/CustomerDetails"; import { CustomerDetails_user } from "../../types/CustomerDetails";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -39,7 +40,7 @@ export interface CustomerDetailsProps {
note: string; note: string;
}; };
disabled: boolean; disabled: boolean;
errors: FormErrors<"isActive" | "note">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -90,10 +91,10 @@ const CustomerDetails: React.FC<CustomerDetailsProps> = props => {
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.note} error={!!getFieldError(errors, "note")}
fullWidth fullWidth
multiline multiline
helperText={errors.note} helperText={getFieldError(errors, "note")?.message}
name="note" name="note"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Note", defaultMessage: "Note",

View file

@ -56,7 +56,6 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
return ( return (
<Form <Form
errors={errors}
initial={{ initial={{
email: maybe(() => customer.email, ""), email: maybe(() => customer.email, ""),
firstName: maybe(() => customer.firstName, ""), firstName: maybe(() => customer.firstName, ""),
@ -67,7 +66,7 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
onSubmit={onSubmit} onSubmit={onSubmit}
confirmLeave confirmLeave
> >
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.customers)} {intl.formatMessage(sectionNames.customers)}
@ -79,14 +78,14 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
customer={customer} customer={customer}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
<CustomerInfo <CustomerInfo
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -10,6 +10,8 @@ import CardTitle from "@saleor/components/CardTitle";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
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 { getFieldError } from "@saleor/utils/errors";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -33,11 +35,7 @@ export interface CustomerInfoProps {
email: string; email: string;
}; };
disabled: boolean; disabled: boolean;
errors: { errors: UserError[];
firstName?: string;
lastName?: string;
email?: string;
};
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -64,9 +62,9 @@ const CustomerInfo: React.FC<CustomerInfoProps> = props => {
<Grid variant="uniform"> <Grid variant="uniform">
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.firstName} error={!!getFieldError(errors, "firstName")}
fullWidth fullWidth
helperText={errors.firstName} helperText={getFieldError(errors, "firstName")?.message}
name="firstName" name="firstName"
type="text" type="text"
label={intl.formatMessage(commonMessages.firstName)} label={intl.formatMessage(commonMessages.firstName)}
@ -75,9 +73,9 @@ const CustomerInfo: React.FC<CustomerInfoProps> = props => {
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.lastName} error={!!getFieldError(errors, "lastName")}
fullWidth fullWidth
helperText={errors.lastName} helperText={getFieldError(errors, "lastName")?.message}
name="lastName" name="lastName"
type="text" type="text"
label={intl.formatMessage(commonMessages.lastName)} label={intl.formatMessage(commonMessages.lastName)}
@ -94,9 +92,9 @@ const CustomerInfo: React.FC<CustomerInfoProps> = props => {
</Typography> </Typography>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.email} error={!!getFieldError(errors, "email")}
fullWidth fullWidth
helperText={errors.email} helperText={getFieldError(errors, "email")?.message}
name="email" name="email"
type="email" type="email"
label={intl.formatMessage(commonMessages.email)} label={intl.formatMessage(commonMessages.email)}

View file

@ -8,7 +8,8 @@ import CardTitle from "@saleor/components/CardTitle";
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
interface DiscountDatesProps { interface DiscountDatesProps {
data: { data: {
@ -20,7 +21,7 @@ interface DiscountDatesProps {
}; };
defaultCurrency: string; defaultCurrency: string;
disabled: boolean; disabled: boolean;
errors: FormErrors<"endDate" | "startDate">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -44,8 +45,8 @@ const DiscountDates = ({
<Grid variant="uniform"> <Grid variant="uniform">
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.startDate} error={!!getFieldError(errors, "startDate")}
helperText={errors.startDate} helperText={getFieldError(errors, "startDate")?.message}
name={"startDate" as keyof FormData} name={"startDate" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.startDate)} label={intl.formatMessage(commonMessages.startDate)}
@ -58,8 +59,8 @@ const DiscountDates = ({
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.startDate} error={!!getFieldError(errors, "startDate")}
helperText={errors.startDate} helperText={getFieldError(errors, "startDate")?.message}
name={"startTime" as keyof FormData} name={"startTime" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.startHour)} label={intl.formatMessage(commonMessages.startHour)}
@ -84,8 +85,8 @@ const DiscountDates = ({
<Grid variant="uniform"> <Grid variant="uniform">
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.endDate} error={!!getFieldError(errors, "endDate")}
helperText={errors.endDate} helperText={getFieldError(errors, "endDate")?.message}
name={"endDate" as keyof FormData} name={"endDate" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.endDate)} label={intl.formatMessage(commonMessages.endDate)}
@ -98,8 +99,8 @@ const DiscountDates = ({
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.endDate} error={!!getFieldError(errors, "endDate")}
helperText={errors.endDate} helperText={getFieldError(errors, "endDate")?.message}
name={"endTime" as keyof FormData} name={"endTime" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.endHour)} label={intl.formatMessage(commonMessages.endHour)}

View file

@ -57,8 +57,8 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
value: "" value: ""
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.sales)} {intl.formatMessage(sectionNames.sales)}
@ -74,7 +74,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
<SaleInfo <SaleInfo
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -84,7 +84,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
</div> </div>

View file

@ -121,8 +121,8 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
value: maybe(() => sale.value.toString(), "") value: maybe(() => sale.value.toString(), "")
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.sales)} {intl.formatMessage(sectionNames.sales)}
@ -133,7 +133,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
<SaleInfo <SaleInfo
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -143,7 +143,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
currencySymbol={defaultCurrency} currencySymbol={defaultCurrency}
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -258,7 +258,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
</div> </div>

View file

@ -6,14 +6,14 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { FormData } from "../SaleDetailsPage"; import { FormData } from "../SaleDetailsPage";
export interface SaleInfoProps { export interface SaleInfoProps {
data: FormData; data: FormData;
disabled: boolean; disabled: boolean;
errors: { errors: UserError[];
name?: string;
};
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -33,8 +33,8 @@ const SaleInfo: React.FC<SaleInfoProps> = ({
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "name")}
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
name={"name" as keyof FormData} name={"name" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage({ label={intl.formatMessage({

View file

@ -6,15 +6,16 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { FormChange } from "@saleor/hooks/useForm"; import { FormChange } from "@saleor/hooks/useForm";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { SaleType } from "@saleor/types/globalTypes"; import { SaleType } from "@saleor/types/globalTypes";
import { getFieldError } from "@saleor/utils/errors";
import { FormData } from "../SaleDetailsPage"; import { FormData } from "../SaleDetailsPage";
export interface SaleValueProps { export interface SaleValueProps {
currencySymbol: string; currencySymbol: string;
data: FormData; data: FormData;
disabled: boolean; disabled: boolean;
errors: FormErrors<"value">; errors: UserError[];
onChange: FormChange; onChange: FormChange;
} }
@ -43,12 +44,12 @@ const SaleValue: React.FC<SaleValueProps> = ({
defaultMessage: "Discount Value", defaultMessage: "Discount Value",
description: "sale discount" description: "sale discount"
})} })}
error={!!errors.value} error={!!getFieldError(errors, "value")}
name="value" name="value"
InputProps={{ InputProps={{
endAdornment: data.type === SaleType.FIXED ? currencySymbol : "%" endAdornment: data.type === SaleType.FIXED ? currencySymbol : "%"
}} }}
helperText={errors.value} helperText={getFieldError(errors, "value")?.message}
value={data.value} value={data.value}
onChange={onChange} onChange={onChange}
/> />

View file

@ -81,8 +81,8 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.vouchers)} {intl.formatMessage(sectionNames.vouchers)}
@ -97,7 +97,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
<div> <div>
<VoucherInfo <VoucherInfo
data={data} data={data}
errors={formErrors} errors={errors}
disabled={disabled} disabled={disabled}
onChange={change} onChange={change}
variant="create" variant="create"
@ -106,7 +106,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
<VoucherTypes <VoucherTypes
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
{data.discountType.toString() !== "SHIPPING" ? ( {data.discountType.toString() !== "SHIPPING" ? (
@ -114,7 +114,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
variant="create" variant="create"
/> />
@ -124,7 +124,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -132,7 +132,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -140,7 +140,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
</div> </div>

View file

@ -8,14 +8,15 @@ import CardTitle from "@saleor/components/CardTitle";
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox"; import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
import { FormData } from "../VoucherDetailsPage"; import { FormData } from "../VoucherDetailsPage";
interface VoucherDatesProps { interface VoucherDatesProps {
data: FormData; data: FormData;
defaultCurrency: string; defaultCurrency: string;
disabled: boolean; disabled: boolean;
errors: FormErrors<"endDate" | "startDate">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -39,8 +40,8 @@ const VoucherDates = ({
<Grid variant="uniform"> <Grid variant="uniform">
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.startDate} error={!!getFieldError(errors, "startDate")}
helperText={errors.startDate} helperText={getFieldError(errors, "startDate")?.message}
name={"startDate" as keyof FormData} name={"startDate" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.startDate)} label={intl.formatMessage(commonMessages.startDate)}
@ -53,8 +54,8 @@ const VoucherDates = ({
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.startDate} error={!!getFieldError(errors, "startDate")}
helperText={errors.startDate} helperText={getFieldError(errors, "startDate")?.message}
name={"startTime" as keyof FormData} name={"startTime" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.startHour)} label={intl.formatMessage(commonMessages.startHour)}
@ -79,8 +80,8 @@ const VoucherDates = ({
<Grid variant="uniform"> <Grid variant="uniform">
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.endDate} error={!!getFieldError(errors, "endDate")}
helperText={errors.endDate} helperText={getFieldError(errors, "endDate")?.message}
name={"endDate" as keyof FormData} name={"endDate" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.endDate)} label={intl.formatMessage(commonMessages.endDate)}
@ -93,8 +94,8 @@ const VoucherDates = ({
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.endDate} error={!!getFieldError(errors, "endDate")}
helperText={errors.endDate} helperText={getFieldError(errors, "endDate")?.message}
name={"endTime" as keyof FormData} name={"endTime" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage(commonMessages.endHour)} label={intl.formatMessage(commonMessages.endHour)}

View file

@ -166,8 +166,8 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.vouchers)} {intl.formatMessage(sectionNames.vouchers)}
@ -178,7 +178,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
<VoucherInfo <VoucherInfo
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
variant="update" variant="update"
/> />
@ -186,7 +186,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
<VoucherTypes <VoucherTypes
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -195,7 +195,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
variant="update" variant="update"
/> />
@ -337,7 +337,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -345,7 +345,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
@ -353,7 +353,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
data={data} data={data}
disabled={disabled} disabled={disabled}
defaultCurrency={defaultCurrency} defaultCurrency={defaultCurrency}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
</div> </div>

View file

@ -7,13 +7,14 @@ import { FormattedMessage, useIntl } from "react-intl";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getFieldError } from "@saleor/utils/errors";
import { generateCode } from "../../../misc"; import { generateCode } from "../../../misc";
import { FormErrors } from "../../../types"; import { UserError } from "../../../types";
import { FormData } from "../VoucherDetailsPage"; import { FormData } from "../VoucherDetailsPage";
interface VoucherInfoProps { interface VoucherInfoProps {
data: FormData; data: FormData;
errors: FormErrors<"code">; errors: UserError[];
disabled: boolean; disabled: boolean;
variant: "create" | "update"; variant: "create" | "update";
onChange: (event: any) => void; onChange: (event: any) => void;
@ -54,9 +55,9 @@ const VoucherInfo = ({
<CardContent> <CardContent>
<TextField <TextField
disabled={variant === "update" || disabled} disabled={variant === "update" || disabled}
error={!!errors.code} error={!!getFieldError(errors, "code")}
fullWidth fullWidth
helperText={errors.code} helperText={getFieldError(errors, "code")?.message}
name={"code" as keyof FormData} name={"code" as keyof FormData}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Discount Code" defaultMessage: "Discount Code"

View file

@ -6,14 +6,15 @@ 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 { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
import { FormData } from "../VoucherDetailsPage"; import { FormData } from "../VoucherDetailsPage";
interface VoucherLimitsProps { interface VoucherLimitsProps {
data: FormData; data: FormData;
defaultCurrency: string; defaultCurrency: string;
disabled: boolean; disabled: boolean;
errors: FormErrors<"usageLimit">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -46,8 +47,8 @@ const VoucherLimits = ({
{data.hasUsageLimit && ( {data.hasUsageLimit && (
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.usageLimit} error={!!getFieldError(errors, "usageLimit")}
helperText={errors.usageLimit} helperText={getFieldError(errors, "usageLimit")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Limit of Uses", defaultMessage: "Limit of Uses",
description: "voucher" description: "voucher"

View file

@ -8,14 +8,15 @@ import CardTitle from "@saleor/components/CardTitle";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import RadioGroupField from "@saleor/components/RadioGroupField"; import RadioGroupField from "@saleor/components/RadioGroupField";
import { RequirementsPicker } from "@saleor/discounts/types"; import { RequirementsPicker } from "@saleor/discounts/types";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { FormData } from "../VoucherDetailsPage"; import { FormData } from "../VoucherDetailsPage";
interface VoucherRequirementsProps { interface VoucherRequirementsProps {
data: FormData; data: FormData;
defaultCurrency: string; defaultCurrency: string;
disabled: boolean; disabled: boolean;
errors: FormErrors<"minSpent" | "minCheckoutItemsQuantity">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -76,8 +77,8 @@ const VoucherRequirements = ({
{data.requirementsPicker === RequirementsPicker.ORDER ? ( {data.requirementsPicker === RequirementsPicker.ORDER ? (
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.minSpent} error={!!getFieldError(errors, "minSpent")}
helperText={errors.minSpent} helperText={getFieldError(errors, "minSpent")?.message}
label={minimalOrderValueText} label={minimalOrderValueText}
name={"minSpent" as keyof FormData} name={"minSpent" as keyof FormData}
value={data.minSpent} value={data.minSpent}
@ -87,8 +88,10 @@ const VoucherRequirements = ({
) : data.requirementsPicker === RequirementsPicker.ITEM ? ( ) : data.requirementsPicker === RequirementsPicker.ITEM ? (
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.minCheckoutItemsQuantity} error={!!getFieldError(errors, "minCheckoutItemsQuantity")}
helperText={errors.minCheckoutItemsQuantity} helperText={
getFieldError(errors, "minCheckoutItemsQuantity")?.message
}
label={minimalQuantityText} label={minimalQuantityText}
name={"minCheckoutItemsQuantity" as keyof FormData} name={"minCheckoutItemsQuantity" as keyof FormData}
value={data.minCheckoutItemsQuantity} value={data.minCheckoutItemsQuantity}

View file

@ -6,13 +6,14 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import RadioGroupField from "@saleor/components/RadioGroupField"; import RadioGroupField from "@saleor/components/RadioGroupField";
import { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
import { DiscountValueTypeEnum } from "../../../types/globalTypes"; import { DiscountValueTypeEnum } from "../../../types/globalTypes";
import { FormData } from "../VoucherDetailsPage"; import { FormData } from "../VoucherDetailsPage";
interface VoucherTypesProps { interface VoucherTypesProps {
data: FormData; data: FormData;
errors: FormErrors<"discountType">; errors: UserError[];
disabled: boolean; disabled: boolean;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -62,8 +63,8 @@ const VoucherTypes = ({
<RadioGroupField <RadioGroupField
choices={voucherTypeChoices} choices={voucherTypeChoices}
disabled={disabled} disabled={disabled}
error={!!errors.discountType} error={!!getFieldError(errors, "discountType")}
hint={errors.discountType} hint={getFieldError(errors, "discountType")?.message}
name={"discountType" as keyof FormData} name={"discountType" as keyof FormData}
value={data.discountType} value={data.discountType}
onChange={onChange} onChange={onChange}

View file

@ -11,7 +11,8 @@ import { FormSpacer } from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import RadioGroupField from "@saleor/components/RadioGroupField"; import RadioGroupField from "@saleor/components/RadioGroupField";
import TextFieldWithChoice from "@saleor/components/TextFieldWithChoice"; import TextFieldWithChoice from "@saleor/components/TextFieldWithChoice";
import { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
import { DiscountValueTypeEnum } from "../../../types/globalTypes"; import { DiscountValueTypeEnum } from "../../../types/globalTypes";
import { translateVoucherTypes } from "../../translations"; import { translateVoucherTypes } from "../../translations";
import { FormData } from "../VoucherDetailsPage"; import { FormData } from "../VoucherDetailsPage";
@ -19,7 +20,7 @@ import { FormData } from "../VoucherDetailsPage";
interface VoucherValueProps { interface VoucherValueProps {
data: FormData; data: FormData;
defaultCurrency: string; defaultCurrency: string;
errors: FormErrors<"discountValue" | "type">; errors: UserError[];
disabled: boolean; disabled: boolean;
variant: string; variant: string;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
@ -64,7 +65,7 @@ const VoucherValue: React.FC<VoucherValueProps> = props => {
<CardContent> <CardContent>
<TextFieldWithChoice <TextFieldWithChoice
disabled={disabled} disabled={disabled}
error={!!errors.discountValue} error={!!getFieldError(errors, "discountValue")}
ChoiceProps={{ ChoiceProps={{
label: label:
data.discountType === DiscountValueTypeEnum.FIXED data.discountType === DiscountValueTypeEnum.FIXED
@ -73,7 +74,7 @@ const VoucherValue: React.FC<VoucherValueProps> = props => {
name: "discountType" as keyof FormData, name: "discountType" as keyof FormData,
values: null values: null
}} }}
helperText={errors.discountValue} helperText={getFieldError(errors, "discountValue")?.message}
name={"value" as keyof FormData} name={"value" as keyof FormData}
onChange={onChange} onChange={onChange}
label={intl.formatMessage({ label={intl.formatMessage({
@ -93,8 +94,8 @@ const VoucherValue: React.FC<VoucherValueProps> = props => {
<RadioGroupField <RadioGroupField
choices={voucherTypeChoices} choices={voucherTypeChoices}
disabled={disabled} disabled={disabled}
error={!!errors.type} error={!!getFieldError(errors, "type")}
hint={errors.type} hint={getFieldError(errors, "type")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Voucher Specific Information" defaultMessage: "Voucher Specific Information"
})} })}

View file

@ -1,7 +1,6 @@
import isEqual from "lodash-es/isEqual"; import isEqual from "lodash-es/isEqual";
import { useState } from "react"; import { useState } from "react";
import { UserError } from "@saleor/types";
import { toggle } from "@saleor/utils/lists"; import { toggle } from "@saleor/utils/lists";
import useStateFromProps from "./useStateFromProps"; import useStateFromProps from "./useStateFromProps";
@ -17,7 +16,6 @@ export type FormChange = (event: ChangeEvent, cb?: () => void) => void;
export interface UseFormResult<T> { export interface UseFormResult<T> {
change: FormChange; change: FormChange;
data: T; data: T;
errors: Record<string, string>;
hasChanged: boolean; hasChanged: boolean;
reset: () => void; reset: () => void;
set: (data: T) => void; set: (data: T) => void;
@ -26,21 +24,6 @@ export interface UseFormResult<T> {
toggleValue: FormChange; toggleValue: FormChange;
} }
function parseErrors(errors: UserError[]): Record<string, string> {
return errors
? errors.reduce(
(acc, curr) =>
curr.field
? {
...acc,
[curr.field.split(":")[0]]: curr.message
}
: acc,
{}
)
: {};
}
type FormData = Record<string, any | any[]>; type FormData = Record<string, any | any[]>;
function merge<T extends FormData>(prevData: T, prevState: T, data: T): T { function merge<T extends FormData>(prevData: T, prevState: T, data: T): T {
@ -68,7 +51,6 @@ function handleRefresh<T extends FormData>(
function useForm<T extends FormData>( function useForm<T extends FormData>(
initial: T, initial: T,
errors: UserError[],
onSubmit: (data: T) => void onSubmit: (data: T) => void
): UseFormResult<T> { ): UseFormResult<T> {
const [hasChanged, setChanged] = useState(false); const [hasChanged, setChanged] = useState(false);
@ -135,7 +117,6 @@ function useForm<T extends FormData>(
return { return {
change, change,
data, data,
errors: parseErrors(errors),
hasChanged, hasChanged,
reset, reset,
set, set,

View file

@ -12,6 +12,8 @@ import ConfirmButton, {
} from "@saleor/components/ConfirmButton"; } from "@saleor/components/ConfirmButton";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import { buttonMessages } from "@saleor/intl"; import { buttonMessages } from "@saleor/intl";
import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "@saleor/types";
export interface MenuCreateDialogFormData { export interface MenuCreateDialogFormData {
name: string; name: string;
@ -20,6 +22,7 @@ export interface MenuCreateDialogFormData {
export interface MenuCreateDialogProps { export interface MenuCreateDialogProps {
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
disabled: boolean; disabled: boolean;
errors: UserError[];
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
onConfirm: (data: MenuCreateDialogFormData) => void; onConfirm: (data: MenuCreateDialogFormData) => void;
@ -32,6 +35,7 @@ const initialForm: MenuCreateDialogFormData = {
const MenuCreateDialog: React.FC<MenuCreateDialogProps> = ({ const MenuCreateDialog: React.FC<MenuCreateDialogProps> = ({
confirmButtonState, confirmButtonState,
disabled, disabled,
errors,
onClose, onClose,
onConfirm, onConfirm,
open open
@ -48,14 +52,14 @@ const MenuCreateDialog: React.FC<MenuCreateDialogProps> = ({
/> />
</DialogTitle> </DialogTitle>
<Form initial={initialForm} onSubmit={onConfirm}> <Form initial={initialForm} onSubmit={onConfirm}>
{({ change, data, errors: formErrors, submit }) => ( {({ change, data, submit }) => (
<> <>
<DialogContent> <DialogContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!formErrors.name} error={!!getFieldError(errors, "name")}
fullWidth fullWidth
helperText={formErrors.name} helperText={getFieldError(errors, "name")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Menu Title", defaultMessage: "Menu Title",
id: "menuCreateDialogMenuTitleLabel" id: "menuCreateDialogMenuTitleLabel"

View file

@ -253,7 +253,7 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
} }
name="name" name="name"
error={!!getFieldError(errors, "name")} error={!!getFieldError(errors, "name")}
helperText={getFieldError(errors, "name")} helperText={getFieldError(errors, "name")?.message}
/> />
<FormSpacer /> <FormSpacer />
<AutocompleteSelectMenu <AutocompleteSelectMenu
@ -269,7 +269,7 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
loading={loading} loading={loading}
options={options} options={options}
error={!!idError} error={!!idError}
helperText={idError} helperText={idError?.message}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
defaultMessage: "Start typing to begin search...", defaultMessage: "Start typing to begin search...",
id: "menuItemDialogLinkPlaceholder" id: "menuItemDialogLinkPlaceholder"

View file

@ -174,6 +174,7 @@ const MenuList: React.FC<MenuListProps> = ({ params }) => {
open={params.action === "add"} open={params.action === "add"}
confirmButtonState={menuCreateOpts.status} confirmButtonState={menuCreateOpts.status}
disabled={menuCreateOpts.loading} disabled={menuCreateOpts.loading}
errors={menuCreateOpts?.data?.menuCreate.errors || []}
onClose={closeModal} onClose={closeModal}
onConfirm={formData => onConfirm={formData =>
menuCreate({ menuCreate({

View file

@ -80,8 +80,8 @@ const OrderAddressEditDialog: React.FC<OrderAddressEditDialogProps> = props => {
return ( return (
<Dialog onClose={onClose} open={open} classes={{ paper: classes.overflow }}> <Dialog onClose={onClose} open={open} classes={{ paper: classes.overflow }}>
<Form initial={address} errors={dialogErrors} onSubmit={handleSubmit}> <Form initial={address} onSubmit={handleSubmit}>
{({ change, data, errors, submit }) => { {({ change, data, submit }) => {
const handleCountrySelect = createSingleAutocompleteSelectHandler( const handleCountrySelect = createSingleAutocompleteSelectHandler(
change, change,
setCountryDisplayName, setCountryDisplayName,
@ -106,7 +106,7 @@ const OrderAddressEditDialog: React.FC<OrderAddressEditDialogProps> = props => {
countries={countryChoices} countries={countryChoices}
countryDisplayValue={countryDisplayName} countryDisplayValue={countryDisplayName}
data={data} data={data}
errors={errors} errors={dialogErrors}
onChange={change} onChange={change}
onCountryChange={handleCountrySelect} onCountryChange={handleCountrySelect}
/> />

View file

@ -70,8 +70,8 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
title: maybe(() => page.title, "") title: maybe(() => page.title, "")
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.pages)} {intl.formatMessage(sectionNames.pages)}
@ -91,7 +91,7 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
<PageInfo <PageInfo
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
page={page} page={page}
onChange={change} onChange={change}
/> />
@ -119,13 +119,13 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
<PageSlug <PageSlug
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />
<VisibilityCard <VisibilityCard
data={data} data={data}
errors={formErrors} errors={errors}
disabled={disabled} disabled={disabled}
hiddenMessage={intl.formatMessage( hiddenMessage={intl.formatMessage(
{ {

View file

@ -9,15 +9,16 @@ 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 { getFieldError } from "@saleor/utils/errors";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { FormErrors } from "../../../types"; import { UserError } from "../../../types";
import { PageDetails_page } from "../../types/PageDetails"; import { PageDetails_page } from "../../types/PageDetails";
import { FormData } from "../PageDetailsPage"; import { FormData } from "../PageDetailsPage";
export interface PageInfoProps { export interface PageInfoProps {
data: FormData; data: FormData;
disabled: boolean; disabled: boolean;
errors: FormErrors<"contentJson" | "title">; errors: UserError[];
page: PageDetails_page; page: PageDetails_page;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -45,9 +46,9 @@ const PageInfo: React.FC<PageInfoProps> = props => {
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.title} error={!!getFieldError(errors, "title")}
fullWidth fullWidth
helperText={errors.title} helperText={getFieldError(errors, "title")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Title", defaultMessage: "Title",
description: "page title" description: "page title"
@ -59,8 +60,8 @@ const PageInfo: React.FC<PageInfoProps> = props => {
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
disabled={disabled} disabled={disabled}
error={!!errors.contentJson} error={!!getFieldError(errors, "contentJson")}
helperText={errors.contentJson} helperText={getFieldError(errors, "contentJson")?.message}
initial={maybe(() => JSON.parse(page.contentJson))} initial={maybe(() => JSON.parse(page.contentJson))}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Content", defaultMessage: "Content",

View file

@ -6,12 +6,14 @@ import { useIntl } from "react-intl";
import slugify from "slugify"; import slugify from "slugify";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { FormData } from "../PageDetailsPage"; import { FormData } from "../PageDetailsPage";
export interface PageSlugProps { export interface PageSlugProps {
data: FormData; data: FormData;
disabled: boolean; disabled: boolean;
errors: Partial<Record<"slug", string>>; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -34,13 +36,13 @@ const PageSlug: React.FC<PageSlugProps> = ({
<TextField <TextField
name={"slug" as keyof FormData} name={"slug" as keyof FormData}
disabled={disabled} disabled={disabled}
error={!!errors.slug} error={!!getFieldError(errors, "slug")}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Slug", defaultMessage: "Slug",
description: "page internal name" description: "page internal name"
})} })}
helperText={ helperText={
errors.slug || getFieldError(errors, "slug")?.message ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: defaultMessage:
"If empty, URL will be autogenerated from Page Name" "If empty, URL will be autogenerated from Page Name"

View file

@ -4,17 +4,18 @@ import makeStyles from "@material-ui/core/styles/makeStyles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
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 { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { ConfigurationTypeFieldEnum } from "@saleor/types/globalTypes"; import { ConfigurationTypeFieldEnum } from "@saleor/types/globalTypes";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { Plugin_plugin_configuration } from "@saleor/plugins/types/Plugin"; import { Plugin_plugin_configuration } from "@saleor/plugins/types/Plugin";
import { getFieldError } from "@saleor/utils/errors";
import { FormData } from "../PluginsDetailsPage"; import { FormData } from "../PluginsDetailsPage";
interface PluginSettingsProps { interface PluginSettingsProps {
data: FormData; data: FormData;
errors: FormErrors<"name" | "configuration">; errors: UserError[];
disabled: boolean; disabled: boolean;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
fields: Plugin_plugin_configuration[]; fields: Plugin_plugin_configuration[];
@ -84,7 +85,7 @@ const PluginSettings: React.FC<PluginSettingsProps> = ({
) : ( ) : (
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "name")}
helperText={fieldData.helpText} helperText={fieldData.helpText}
label={fieldData.label} label={fieldData.label}
name={field.name} name={field.name}

View file

@ -77,8 +77,8 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = props => {
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ data, errors, hasChanged, submit, set, triggerChange }) => { {({ data, hasChanged, submit, set, triggerChange }) => {
const onChange = (event: ChangeEvent) => { const onChange = (event: ChangeEvent) => {
const { name, value } = event.target; const { name, value } = event.target;
const newData = { const newData = {

View file

@ -11,6 +11,8 @@ import Form from "@saleor/components/Form";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import ListField from "@saleor/components/ListField"; import ListField from "@saleor/components/ListField";
import { buttonMessages } from "@saleor/intl"; import { buttonMessages } from "@saleor/intl";
import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
export interface FormData { export interface FormData {
name: string; name: string;
@ -22,10 +24,7 @@ export interface FormData {
export interface ProductTypeAttributeEditDialogProps { export interface ProductTypeAttributeEditDialogProps {
disabled: boolean; disabled: boolean;
errors: Array<{ errors: UserError[];
field: string;
message: string;
}>;
name: string; name: string;
opened: boolean; opened: boolean;
title: string; title: string;
@ -37,9 +36,16 @@ export interface ProductTypeAttributeEditDialogProps {
onConfirm: (data: FormData) => void; onConfirm: (data: FormData) => void;
} }
const ProductTypeAttributeEditDialog: React.FC< const ProductTypeAttributeEditDialog: React.FC<ProductTypeAttributeEditDialogProps> = ({
ProductTypeAttributeEditDialogProps disabled,
> = ({ disabled, errors, name, opened, title, values, onClose, onConfirm }) => { errors,
name,
opened,
title,
values,
onClose,
onConfirm
}) => {
const intl = useIntl(); const intl = useIntl();
const initialForm: FormData = { const initialForm: FormData = {
@ -48,19 +54,19 @@ const ProductTypeAttributeEditDialog: React.FC<
}; };
return ( return (
<Dialog onClose={onClose} open={opened}> <Dialog onClose={onClose} open={opened}>
<Form errors={errors} initial={initialForm} onSubmit={onConfirm}> <Form initial={initialForm} onSubmit={onConfirm}>
{({ change, data, errors: formErrors }) => ( {({ change, data }) => (
<> <>
<DialogTitle>{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
<DialogContent> <DialogContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!formErrors.name} error={!!getFieldError(errors, "name")}
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Attribute name" defaultMessage: "Attribute name"
})} })}
helperText={formErrors.name} helperText={getFieldError(errors, "name")?.message}
name="name" name="name"
value={data.name} value={data.name}
onChange={change} onChange={change}
@ -70,9 +76,9 @@ const ProductTypeAttributeEditDialog: React.FC<
autoComplete="off" autoComplete="off"
disabled={disabled} disabled={disabled}
error={ error={
!!formErrors.values || !!getFieldError(errors, "values") ||
!!formErrors.addValues || !!getFieldError(errors, "addValues") ||
!!formErrors.removeValues !!getFieldError(errors, "removeValues")
} }
fullWidth fullWidth
name="values" name="values"
@ -80,9 +86,9 @@ const ProductTypeAttributeEditDialog: React.FC<
defaultMessage: "Attribute values" defaultMessage: "Attribute values"
})} })}
helperText={ helperText={
formErrors.values || getFieldError(errors, "values") ||
formErrors.addValues || getFieldError(errors, "addValues") ||
formErrors.removeValues getFieldError(errors, "removeValues")
} }
values={data.values} values={data.values}
onChange={change} onChange={change}

View file

@ -70,13 +70,8 @@ const ProductTypeCreatePage: React.FC<ProductTypeCreatePageProps> = ({
const [taxTypeDisplayName, setTaxTypeDisplayName] = useStateFromProps(""); const [taxTypeDisplayName, setTaxTypeDisplayName] = useStateFromProps("");
return ( return (
<Form <Form initial={formInitialData} onSubmit={onSubmit} confirmLeave>
errors={errors} {({ change, data, hasChanged, submit }) => (
initial={formInitialData}
onSubmit={onSubmit}
confirmLeave
>
{({ change, data, errors: formErrors, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.productTypes)} {intl.formatMessage(sectionNames.productTypes)}
@ -87,7 +82,7 @@ const ProductTypeCreatePage: React.FC<ProductTypeCreatePageProps> = ({
<ProductTypeDetails <ProductTypeDetails
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -7,7 +7,8 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
const useStyles = makeStyles( const useStyles = makeStyles(
{ {
@ -23,7 +24,7 @@ interface ProductTypeDetailsProps {
name: string; name: string;
}; };
disabled: boolean; disabled: boolean;
errors: FormErrors<"name">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -41,9 +42,9 @@ const ProductTypeDetails: React.FC<ProductTypeDetailsProps> = props => {
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "name")}
fullWidth fullWidth
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Product Type Name" defaultMessage: "Product Type Name"
})} })}

View file

@ -123,13 +123,8 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
weight: maybe(() => productType.weight.value) weight: maybe(() => productType.weight.value)
}; };
return ( return (
<Form <Form initial={formInitialData} onSubmit={onSubmit} confirmLeave>
errors={errors} {({ change, data, hasChanged, submit }) => (
initial={formInitialData}
onSubmit={onSubmit}
confirmLeave
>
{({ change, data, errors: formErrors, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.productTypes)} {intl.formatMessage(sectionNames.productTypes)}
@ -140,7 +135,7 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
<ProductTypeDetails <ProductTypeDetails
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -90,7 +90,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
disabled, disabled,
categories: categoryChoiceList, categories: categoryChoiceList,
collections: collectionChoiceList, collections: collectionChoiceList,
errors: userErrors, errors,
fetchCategories, fetchCategories,
fetchCollections, fetchCollections,
fetchMoreCategories, fetchMoreCategories,
@ -162,21 +162,8 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
}); });
return ( return (
<Form <Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
onSubmit={handleSubmit} {({ change, data, hasChanged, submit, triggerChange, toggleValue }) => {
errors={userErrors}
initial={initialData}
confirmLeave
>
{({
change,
data,
errors,
hasChanged,
submit,
triggerChange,
toggleValue
}) => {
const handleCollectionSelect = createMultiAutocompleteSelectHandler( const handleCollectionSelect = createMultiAutocompleteSelectHandler(
toggleValue, toggleValue,
setSelectedCollections, setSelectedCollections,

View file

@ -9,6 +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 { getFieldError } from "@saleor/utils/errors";
interface ProductDetailsFormProps { interface ProductDetailsFormProps {
data: { data: {
@ -16,7 +18,7 @@ interface ProductDetailsFormProps {
name: string; name: string;
}; };
disabled?: boolean; disabled?: boolean;
errors: { [key: string]: string }; errors: UserError[];
// 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
@ -40,8 +42,8 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
/> />
<CardContent> <CardContent>
<TextField <TextField
error={!!errors.name} error={!!getFieldError(errors, "name")}
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
disabled={disabled} disabled={disabled}
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
@ -55,8 +57,8 @@ export const ProductDetailsForm: React.FC<ProductDetailsFormProps> = ({
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
disabled={disabled} disabled={disabled}
error={!!errors.descriptionJson} error={!!getFieldError(errors, "descriptionJson")}
helperText={errors.descriptionJson} helperText={getFieldError(errors, "descriptionJson")?.message}
initial={initialDescription} initial={initialDescription}
label={intl.formatMessage(commonMessages.description)} label={intl.formatMessage(commonMessages.description)}
name="description" name="description"

View file

@ -17,7 +17,8 @@ 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, FormErrors } from "@saleor/types"; import { FetchMoreProps, UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
interface ProductType { interface ProductType {
hasVariants: boolean; hasVariants: boolean;
@ -53,7 +54,7 @@ interface ProductOrganizationProps {
productType?: string; productType?: string;
}; };
disabled: boolean; disabled: boolean;
errors: FormErrors<"productType" | "category">; errors: UserError[];
productType?: ProductType; productType?: ProductType;
productTypeInputDisplayValue?: string; productTypeInputDisplayValue?: string;
productTypes?: SingleAutocompleteChoiceType[]; productTypes?: SingleAutocompleteChoiceType[];
@ -73,7 +74,6 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
canChangeType, canChangeType,
categories, categories,
categoryInputDisplayValue, categoryInputDisplayValue,
collections, collections,
collectionsInputDisplayValue, collectionsInputDisplayValue,
data, data,
@ -108,8 +108,8 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
{canChangeType ? ( {canChangeType ? (
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
displayValue={productTypeInputDisplayValue} displayValue={productTypeInputDisplayValue}
error={!!errors.productType} error={!!getFieldError(errors, "productType")}
helperText={errors.productType} helperText={getFieldError(errors, "productType")?.message}
name="productType" name="productType"
disabled={disabled} disabled={disabled}
label={intl.formatMessage({ label={intl.formatMessage({
@ -154,8 +154,8 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
<FormSpacer /> <FormSpacer />
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
displayValue={categoryInputDisplayValue} displayValue={categoryInputDisplayValue}
error={!!errors.category} error={!!getFieldError(errors, "category")}
helperText={errors.category} helperText={getFieldError(errors, "category")?.message}
disabled={disabled} disabled={disabled}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Category" defaultMessage: "Category"

View file

@ -6,6 +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 { getFieldError } from "@saleor/utils/errors";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ProductDetails_product } from "../../types/ProductDetails"; import { ProductDetails_product } from "../../types/ProductDetails";
@ -26,7 +28,7 @@ interface ProductStockProps {
stockQuantity: number; stockQuantity: number;
}; };
disabled: boolean; disabled: boolean;
errors: { [key: string]: string }; errors: UserError[];
product: ProductDetails_product; product: ProductDetails_product;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -56,8 +58,8 @@ const ProductStock: React.FC<ProductStockProps> = props => {
})} })}
value={data.sku} value={data.sku}
onChange={onChange} onChange={onChange}
error={!!errors.sku} error={!!getFieldError(errors, "sku")}
helperText={errors.sku} helperText={getFieldError(errors, "sku")?.message}
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}

View file

@ -85,7 +85,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
disabled, disabled,
categories: categoryChoiceList, categories: categoryChoiceList,
collections: collectionChoiceList, collections: collectionChoiceList,
errors: userErrors, errors,
fetchCategories, fetchCategories,
fetchCollections, fetchCollections,
fetchMoreCategories, fetchMoreCategories,
@ -152,21 +152,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
}); });
return ( return (
<Form <Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
onSubmit={handleSubmit} {({ change, data, hasChanged, submit, triggerChange, toggleValue }) => {
errors={userErrors}
initial={initialData}
confirmLeave
>
{({
change,
data,
errors,
hasChanged,
submit,
triggerChange,
toggleValue
}) => {
const handleCollectionSelect = createMultiAutocompleteSelectHandler( const handleCollectionSelect = createMultiAutocompleteSelectHandler(
toggleValue, toggleValue,
setSelectedCollections, setSelectedCollections,

View file

@ -51,7 +51,7 @@ interface ProductVariantCreatePageProps {
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
currencySymbol, currencySymbol,
errors: apiErrors, errors,
loading, loading,
header, header,
product, product,
@ -93,8 +93,8 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
}); });
return ( return (
<Form initial={initialForm} errors={apiErrors} onSubmit={handleSubmit}> <Form initial={initialForm} onSubmit={handleSubmit}>
{({ change, data, errors, hasChanged, submit, triggerChange }) => { {({ change, data, hasChanged, submit, triggerChange }) => {
const handleAttributeChange: FormsetChange = (id, value) => { const handleAttributeChange: FormsetChange = (id, value) => {
changeAttributeData(id, value); changeAttributeData(id, value);
triggerChange(); triggerChange();
@ -120,7 +120,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
<ProductVariantAttributes <ProductVariantAttributes
attributes={attributes} attributes={attributes}
disabled={loading} disabled={loading}
errors={apiErrors} errors={errors}
onChange={handleAttributeChange} onChange={handleAttributeChange}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -53,7 +53,7 @@ interface ProductVariantPageProps {
} }
const ProductVariantPage: React.FC<ProductVariantPageProps> = ({ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
errors: apiErrors, errors,
loading, loading,
header, header,
placeholderImage, placeholderImage,
@ -109,13 +109,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
{maybe(() => variant.product.name)} {maybe(() => variant.product.name)}
</AppHeader> </AppHeader>
<PageHeader title={header} /> <PageHeader title={header} />
<Form <Form initial={initialForm} onSubmit={handleSubmit} confirmLeave>
initial={initialForm} {({ change, data, hasChanged, submit, triggerChange }) => {
errors={apiErrors}
onSubmit={handleSubmit}
confirmLeave
>
{({ change, data, errors, hasChanged, submit, triggerChange }) => {
const handleAttributeChange: FormsetChange = (id, value) => { const handleAttributeChange: FormsetChange = (id, value) => {
changeAttributeData(id, value); changeAttributeData(id, value);
triggerChange(); triggerChange();
@ -143,7 +138,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
<ProductVariantAttributes <ProductVariantAttributes
attributes={attributes} attributes={attributes}
disabled={loading} disabled={loading}
errors={apiErrors} errors={errors}
onChange={handleAttributeChange} onChange={handleAttributeChange}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -6,6 +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 { getFieldError } from "@saleor/utils/errors";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -22,7 +24,7 @@ interface ProductVariantPriceProps {
currencySymbol?: string; currencySymbol?: string;
priceOverride?: string; priceOverride?: string;
costPrice?: string; costPrice?: string;
errors: { [key: string]: string }; errors: UserError[];
loading?: boolean; loading?: boolean;
onChange(event: any); onChange(event: any);
} }
@ -52,19 +54,18 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
<div className={classes.grid}> <div className={classes.grid}>
<div> <div>
<PriceField <PriceField
error={!!errors.price_override} error={!!getFieldError(errors, "price_override")}
name="priceOverride" name="priceOverride"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Selling price override" defaultMessage: "Selling price override"
})} })}
hint={ hint={
errors.price_override getFieldError(errors, "price_override")?.message ||
? errors.price_override intl.formatMessage({
: intl.formatMessage({ defaultMessage: "Optional",
defaultMessage: "Optional", description: "optional field",
description: "optional field", id: "productVariantPriceOptionalPriceOverrideField"
id: "productVariantPriceOptionalPriceOverrideField" })
})
} }
value={priceOverride} value={priceOverride}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
@ -74,19 +75,18 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
</div> </div>
<div> <div>
<PriceField <PriceField
error={!!errors.cost_price} error={!!getFieldError(errors, "cost_price")}
name="costPrice" name="costPrice"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Cost price override" defaultMessage: "Cost price override"
})} })}
hint={ hint={
errors.cost_price getFieldError(errors, "cost_price")?.message ||
? errors.cost_price intl.formatMessage({
: intl.formatMessage({ defaultMessage: "Optional",
defaultMessage: "Optional", description: "optional field",
description: "optional field", id: "productVariantPriceOptionalCostPriceField"
id: "productVariantPriceOptionalCostPriceField" })
})
} }
value={costPrice} value={costPrice}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}

View file

@ -6,6 +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 { getFieldError } from "@saleor/utils/errors";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -19,10 +21,7 @@ const useStyles = makeStyles(
); );
interface ProductVariantStockProps { interface ProductVariantStockProps {
errors: { errors: UserError[];
quantity?: string;
sku?: string;
};
sku: string; sku: string;
quantity: string; quantity: string;
stockAllocated?: number; stockAllocated?: number;
@ -48,7 +47,7 @@ const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
<div className={classes.grid}> <div className={classes.grid}>
<div> <div>
<TextField <TextField
error={!!errors.quantity} error={!!getFieldError(errors, "quantity")}
name="quantity" name="quantity"
value={quantity} value={quantity}
label={intl.formatMessage({ label={intl.formatMessage({
@ -56,8 +55,8 @@ const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
description: "product variant stock" description: "product variant stock"
})} })}
helperText={ helperText={
errors.quantity getFieldError(errors, "quantity")
? errors.quantity ? getFieldError(errors, "quantity")
: !!stockAllocated : !!stockAllocated
? intl.formatMessage( ? intl.formatMessage(
{ {
@ -77,8 +76,8 @@ const ProductVariantStock: React.FC<ProductVariantStockProps> = props => {
</div> </div>
<div> <div>
<TextField <TextField
error={!!errors.sku} error={!!getFieldError(errors, "sku")}
helperText={errors.sku} helperText={getFieldError(errors, "sku")?.message}
name="sku" name="sku"
value={sku} value={sku}
label={intl.formatMessage({ label={intl.formatMessage({

View file

@ -35,7 +35,7 @@ export interface ServiceCreatePageProps {
const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => { const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
const { const {
disabled, disabled,
errors: formErrors, errors,
permissions, permissions,
saveButtonBarState, saveButtonBarState,
onBack, onBack,
@ -50,13 +50,8 @@ const ServiceCreatePage: React.FC<ServiceCreatePageProps> = props => {
permissions: [] permissions: []
}; };
return ( return (
<Form <Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
errors={formErrors} {({ data, change, hasChanged, submit }) => (
initial={initialForm}
onSubmit={onSubmit}
confirmLeave
>
{({ data, change, errors, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.serviceAccounts)} {intl.formatMessage(sectionNames.serviceAccounts)}

View file

@ -48,7 +48,7 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
const { const {
apiUri, apiUri,
disabled, disabled,
errors: formErrors, errors,
permissions, permissions,
saveButtonBarState, saveButtonBarState,
service, service,
@ -79,13 +79,8 @@ const ServiceDetailsPage: React.FC<ServiceDetailsPageProps> = props => {
permissions: maybe(() => service.permissions, []).map(perm => perm.code) permissions: maybe(() => service.permissions, []).map(perm => perm.code)
}; };
return ( return (
<Form <Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
errors={formErrors} {({ data, change, hasChanged, submit }) => (
initial={initialForm}
onSubmit={onSubmit}
confirmLeave
>
{({ data, change, errors, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.serviceAccounts)} {intl.formatMessage(sectionNames.serviceAccounts)}

View file

@ -6,14 +6,15 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { FormChange } from "@saleor/hooks/useForm"; import { FormChange } from "@saleor/hooks/useForm";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
export interface ServiceInfoProps { export interface ServiceInfoProps {
data: { data: {
name: string; name: string;
}; };
disabled: boolean; disabled: boolean;
errors: FormErrors<"name">; errors: UserError[];
onChange: FormChange; onChange: FormChange;
} }
@ -32,12 +33,12 @@ const ServiceInfo: React.FC<ServiceInfoProps> = props => {
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "name")}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Account Name", defaultMessage: "Account Name",
description: "service account" description: "service account"
})} })}
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
fullWidth fullWidth
name="name" name="name"
value={data.name} value={data.name}

View file

@ -50,8 +50,8 @@ const ShippingZoneCreatePage: React.FC<ShippingZoneCreatePageProps> = ({
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<> <>
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
@ -67,7 +67,7 @@ const ShippingZoneCreatePage: React.FC<ShippingZoneCreatePageProps> = ({
<div> <div>
<ShippingZoneInfo <ShippingZoneInfo
data={data} data={data}
errors={formErrors} errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -57,11 +57,11 @@ const ShippingZoneDetailsPage: React.FC<ShippingZoneDetailsPageProps> = ({
const intl = useIntl(); const intl = useIntl();
const initialForm: FormData = { const initialForm: FormData = {
name: maybe(() => shippingZone.name, "") name: shippingZone?.name || ""
}; };
return ( return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}> <AppHeader onBack={onBack}>
<FormattedMessage defaultMessage="Shipping" /> <FormattedMessage defaultMessage="Shipping" />
@ -69,11 +69,7 @@ const ShippingZoneDetailsPage: React.FC<ShippingZoneDetailsPageProps> = ({
<PageHeader title={maybe(() => shippingZone.name)} /> <PageHeader title={maybe(() => shippingZone.name)} />
<Grid> <Grid>
<div> <div>
<ShippingZoneInfo <ShippingZoneInfo data={data} errors={errors} onChange={change} />
data={data}
errors={formErrors}
onChange={change}
/>
<CardSpacer /> <CardSpacer />
<CountryList <CountryList
countries={maybe(() => shippingZone.countries)} countries={maybe(() => shippingZone.countries)}

View file

@ -6,12 +6,13 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "../../../types"; import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types";
import { FormData } from "../ShippingZoneDetailsPage"; import { FormData } from "../ShippingZoneDetailsPage";
export interface ShippingZoneInfoProps { export interface ShippingZoneInfoProps {
data: FormData; data: FormData;
errors: FormErrors<"name">; errors: UserError[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -29,9 +30,9 @@ const ShippingZoneInfo: React.FC<ShippingZoneInfoProps> = ({
/> />
<CardContent> <CardContent>
<TextField <TextField
error={!!errors.name} error={!!getFieldError(errors, "name")}
fullWidth fullWidth
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Shipping Zone Name" defaultMessage: "Shipping Zone Name"
})} })}

View file

@ -18,8 +18,9 @@ import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import { buttonMessages } from "@saleor/intl"; import { buttonMessages } from "@saleor/intl";
import { getFieldError } from "@saleor/utils/errors";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { FormErrors, UserError } from "../../../types"; import { UserError } from "../../../types";
import { ShippingMethodTypeEnum } from "../../../types/globalTypes"; import { ShippingMethodTypeEnum } from "../../../types/globalTypes";
import { ShippingZoneDetailsFragment_shippingMethods } from "../../types/ShippingZoneDetailsFragment"; import { ShippingZoneDetailsFragment_shippingMethods } from "../../types/ShippingZoneDetailsFragment";
@ -109,243 +110,233 @@ const ShippingZoneRateDialog: React.FC<ShippingZoneRateDialogProps> = props => {
return ( return (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged }) => { {({ change, data, hasChanged }) => (
const typedFormErrors: FormErrors< <>
| "minimumOrderPrice" <DialogTitle>
| "minimumOrderWeight" {variant === ShippingMethodTypeEnum.PRICE
| "maximumOrderPrice" ? action === "create"
| "maximumOrderWeight"
| "price"
| "name"
> = formErrors;
return (
<>
<DialogTitle>
{variant === ShippingMethodTypeEnum.PRICE
? action === "create"
? intl.formatMessage({
defaultMessage: "Add Price Rate",
description: "dialog header"
})
: intl.formatMessage({
defaultMessage: "Edit Price Rate",
description: "dialog header"
})
: action === "create"
? intl.formatMessage({ ? intl.formatMessage({
defaultMessage: "Add Weight Rate", defaultMessage: "Add Price Rate",
description: description: "dialog header"
"add weight based shipping method, dialog header"
}) })
: intl.formatMessage({ : intl.formatMessage({
defaultMessage: "Edit Weight Rate", defaultMessage: "Edit Price Rate",
description: description: "dialog header"
"edit weight based shipping method, dialog header"
})}
</DialogTitle>
<DialogContent>
<TextField
disabled={disabled}
error={!!typedFormErrors.name}
fullWidth
helperText={
typedFormErrors.name ||
intl.formatMessage({
defaultMessage:
"This will be shown to customers at checkout"
}) })
} : action === "create"
label={intl.formatMessage({ ? intl.formatMessage({
defaultMessage: "Rate Name", defaultMessage: "Add Weight Rate",
description: "shipping method name" description:
"add weight based shipping method, dialog header"
})
: intl.formatMessage({
defaultMessage: "Edit Weight Rate",
description:
"edit weight based shipping method, dialog header"
})} })}
name={"name" as keyof FormData} </DialogTitle>
value={data.name} <DialogContent>
onChange={change} <TextField
/> disabled={disabled}
</DialogContent> error={!!getFieldError(errors, "name")}
<Hr /> fullWidth
<DialogContent> helperText={
{!!variant ? ( getFieldError(errors, "name") ||
<> intl.formatMessage({
<Typography defaultMessage:
className={classes.subheading} "This will be shown to customers at checkout"
variant="subtitle1" })
> }
{variant === ShippingMethodTypeEnum.PRICE label={intl.formatMessage({
? intl.formatMessage({ defaultMessage: "Rate Name",
defaultMessage: "Value range", description: "shipping method name"
description: "order price range" })}
}) name={"name" as keyof FormData}
: intl.formatMessage({ value={data.name}
defaultMessage: "Weight range", onChange={change}
description: "order weight range" />
})} </DialogContent>
</Typography> <Hr />
<ControlledCheckbox <DialogContent>
name={"noLimits" as keyof FormData} {!!variant ? (
label={ <>
<> <Typography
<FormattedMessage className={classes.subheading}
defaultMessage="There are no value limits" variant="subtitle1"
description="shipping method has no value limits" >
/> {variant === ShippingMethodTypeEnum.PRICE
<Typography variant="caption"> ? intl.formatMessage({
{variant === ShippingMethodTypeEnum.PRICE defaultMessage: "Value range",
? intl.formatMessage({ description: "order price range"
defaultMessage: })
"This rate will apply to all orders of all prices" : intl.formatMessage({
}) defaultMessage: "Weight range",
: intl.formatMessage({ description: "order weight range"
defaultMessage:
"This rate will apply to all orders of all weights"
})}
</Typography>
</>
}
checked={data.noLimits}
onChange={change}
disabled={disabled}
/>
{!data.noLimits && (
<>
<FormSpacer />
<div className={classes.grid}>
<TextField
disabled={disabled}
error={
variant === ShippingMethodTypeEnum.PRICE
? !!typedFormErrors.minimumOrderPrice
: !!typedFormErrors.minimumOrderWeight
}
fullWidth
helperText={
variant === ShippingMethodTypeEnum.PRICE
? typedFormErrors.minimumOrderPrice
: typedFormErrors.minimumOrderWeight
}
label={
variant === ShippingMethodTypeEnum.PRICE
? typedFormErrors.minimumOrderPrice ||
intl.formatMessage({
defaultMessage: "Minimal Order Value"
})
: typedFormErrors.minimumOrderWeight ||
intl.formatMessage({
defaultMessage: "Minimal Order Weight"
})
}
name={"minValue" as keyof FormData}
type="number"
value={data.minValue}
onChange={change}
/>
<TextField
disabled={disabled}
error={
variant === ShippingMethodTypeEnum.PRICE
? !!typedFormErrors.maximumOrderPrice
: !!typedFormErrors.maximumOrderWeight
}
fullWidth
helperText={
variant === ShippingMethodTypeEnum.PRICE
? typedFormErrors.maximumOrderPrice
: typedFormErrors.maximumOrderWeight
}
label={
variant === ShippingMethodTypeEnum.PRICE
? typedFormErrors.maximumOrderPrice ||
intl.formatMessage({
defaultMessage: "Maximal Order Value"
})
: typedFormErrors.maximumOrderWeight ||
intl.formatMessage({
defaultMessage: "Maximal Order Weight"
})
}
name={"maxValue" as keyof FormData}
type="number"
value={data.maxValue}
onChange={change}
/>
</div>
</>
)}
</>
) : (
<Skeleton />
)}
</DialogContent>
<Hr />
<DialogContent>
<Typography className={classes.subheading} variant="subtitle1">
<FormattedMessage
defaultMessage="Rate"
description="shipping method"
/>
</Typography>
<ControlledCheckbox
name={"isFree" as keyof FormData}
label={intl.formatMessage({
defaultMessage: "This is free shipping",
description: "shipping method, switch button"
})}
checked={data.isFree}
onChange={change}
disabled={disabled}
/>
{!data.isFree && (
<>
<FormSpacer />
<div className={classes.grid}>
<TextField
disabled={disabled}
error={!!typedFormErrors.price}
fullWidth
helperText={typedFormErrors.price}
label={intl.formatMessage({
defaultMessage: "Rate Price",
description: "shipping method price"
})} })}
name={"price" as keyof FormData} </Typography>
type="number" <ControlledCheckbox
value={data.price} name={"noLimits" as keyof FormData}
onChange={change} label={
InputProps={{ <>
endAdornment: defaultCurrency <FormattedMessage
}} defaultMessage="There are no value limits"
/> description="shipping method has no value limits"
</div> />
</> <Typography variant="caption">
)} {variant === ShippingMethodTypeEnum.PRICE
</DialogContent> ? intl.formatMessage({
<DialogActions> defaultMessage:
<Button onClick={onClose}> "This rate will apply to all orders of all prices"
<FormattedMessage {...buttonMessages.back} /> })
</Button> : intl.formatMessage({
<ConfirmButton defaultMessage:
disabled={disabled || !hasChanged} "This rate will apply to all orders of all weights"
transitionState={confirmButtonState} })}
color="primary" </Typography>
variant="contained" </>
type="submit" }
> checked={data.noLimits}
{action === "create" onChange={change}
? intl.formatMessage({ disabled={disabled}
defaultMessage: "Create rate", />
description: "button" {!data.noLimits && (
}) <>
: intl.formatMessage({ <FormSpacer />
defaultMessage: "Update rate", <div className={classes.grid}>
description: "button" <TextField
disabled={disabled}
error={
variant === ShippingMethodTypeEnum.PRICE
? !!getFieldError(errors, "minimumOrderPrice")
: !!getFieldError(errors, "minimumOrderWeight")
}
fullWidth
helperText={
variant === ShippingMethodTypeEnum.PRICE
? getFieldError(errors, "minimumOrderPrice")
: getFieldError(errors, "minimumOrderWeight")
}
label={
variant === ShippingMethodTypeEnum.PRICE
? getFieldError(errors, "minimumOrderPrice") ||
intl.formatMessage({
defaultMessage: "Minimal Order Value"
})
: getFieldError(errors, "minimumOrderWeight") ||
intl.formatMessage({
defaultMessage: "Minimal Order Weight"
})
}
name={"minValue" as keyof FormData}
type="number"
value={data.minValue}
onChange={change}
/>
<TextField
disabled={disabled}
error={
variant === ShippingMethodTypeEnum.PRICE
? !!getFieldError(errors, "maximumOrderPrice")
: !!getFieldError(errors, "maximumOrderWeight")
}
fullWidth
helperText={
variant === ShippingMethodTypeEnum.PRICE
? getFieldError(errors, "maximumOrderPrice")
: getFieldError(errors, "maximumOrderWeight")
}
label={
variant === ShippingMethodTypeEnum.PRICE
? getFieldError(errors, "maximumOrderPrice") ||
intl.formatMessage({
defaultMessage: "Maximal Order Value"
})
: getFieldError(errors, "maximumOrderWeight") ||
intl.formatMessage({
defaultMessage: "Maximal Order Weight"
})
}
name={"maxValue" as keyof FormData}
type="number"
value={data.maxValue}
onChange={change}
/>
</div>
</>
)}
</>
) : (
<Skeleton />
)}
</DialogContent>
<Hr />
<DialogContent>
<Typography className={classes.subheading} variant="subtitle1">
<FormattedMessage
defaultMessage="Rate"
description="shipping method"
/>
</Typography>
<ControlledCheckbox
name={"isFree" as keyof FormData}
label={intl.formatMessage({
defaultMessage: "This is free shipping",
description: "shipping method, switch button"
})}
checked={data.isFree}
onChange={change}
disabled={disabled}
/>
{!data.isFree && (
<>
<FormSpacer />
<div className={classes.grid}>
<TextField
disabled={disabled}
error={!!getFieldError(errors, "price")}
fullWidth
helperText={getFieldError(errors, "price")?.message}
label={intl.formatMessage({
defaultMessage: "Rate Price",
description: "shipping method price"
})} })}
</ConfirmButton> name={"price" as keyof FormData}
</DialogActions> type="number"
</> value={data.price}
); onChange={change}
}} InputProps={{
endAdornment: defaultCurrency
}}
/>
</div>
</>
)}
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
<FormattedMessage {...buttonMessages.back} />
</Button>
<ConfirmButton
disabled={disabled || !hasChanged}
transitionState={confirmButtonState}
color="primary"
variant="contained"
type="submit"
>
{action === "create"
? intl.formatMessage({
defaultMessage: "Create rate",
description: "button"
})
: intl.formatMessage({
defaultMessage: "Update rate",
description: "button"
})}
</ConfirmButton>
</DialogActions>
</>
)}
</Form> </Form>
</Dialog> </Dialog>
); );

View file

@ -11,16 +11,16 @@ import Grid from "@saleor/components/Grid";
import SingleAutocompleteSelectField, { import SingleAutocompleteSelectField, {
SingleAutocompleteChoiceType SingleAutocompleteChoiceType
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import { AddressTypeInput } from "@saleor/customers/types";
import { ChangeEvent } from "@saleor/hooks/useForm"; import { ChangeEvent } from "@saleor/hooks/useForm";
import { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { SiteSettingsPageFormData } from "../SiteSettingsPage"; import { SiteSettingsPageFormData } from "../SiteSettingsPage";
interface SiteSettingsAddressProps { interface SiteSettingsAddressProps {
countries: SingleAutocompleteChoiceType[]; countries: SingleAutocompleteChoiceType[];
data: SiteSettingsPageFormData; data: SiteSettingsPageFormData;
displayCountry: string; displayCountry: string;
errors: FormErrors<keyof AddressTypeInput>; errors: UserError[];
disabled: boolean; disabled: boolean;
onChange: (event: ChangeEvent) => void; onChange: (event: ChangeEvent) => void;
onCountryChange: (event: ChangeEvent) => void; onCountryChange: (event: ChangeEvent) => void;
@ -60,8 +60,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.companyName} error={!!getFieldError(errors, "companyName")}
helperText={errors.companyName} helperText={getFieldError(errors, "companyName")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Company" defaultMessage: "Company"
})} })}
@ -73,8 +73,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.streetAddress1} error={!!getFieldError(errors, "streetAddress1")}
helperText={errors.streetAddress1} helperText={getFieldError(errors, "streetAddress1")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Address line 1" defaultMessage: "Address line 1"
})} })}
@ -86,8 +86,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.streetAddress2} error={!!getFieldError(errors, "streetAddress2")}
helperText={errors.streetAddress2} helperText={getFieldError(errors, "streetAddress2")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Address line 2" defaultMessage: "Address line 2"
})} })}
@ -100,8 +100,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
<Grid> <Grid>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.city} error={!!getFieldError(errors, "city")}
helperText={errors.city} helperText={getFieldError(errors, "city")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "City" defaultMessage: "City"
})} })}
@ -112,8 +112,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.postalCode} error={!!getFieldError(errors, "postalCode")}
helperText={errors.postalCode} helperText={getFieldError(errors, "postalCode")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "ZIP / Postal code" defaultMessage: "ZIP / Postal code"
})} })}
@ -128,8 +128,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
disabled={disabled} disabled={disabled}
displayValue={displayCountry} displayValue={displayCountry}
error={!!errors.country} error={!!getFieldError(errors, "country")}
helperText={errors.country} helperText={getFieldError(errors, "country")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Country" defaultMessage: "Country"
})} })}
@ -143,8 +143,8 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
/> />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.countryArea} error={!!getFieldError(errors, "companyArea")}
helperText={errors.countryArea} helperText={getFieldError(errors, "companyArea")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Country area" defaultMessage: "Country area"
})} })}
@ -157,9 +157,9 @@ const SiteSettingsAddress: React.FC<SiteSettingsAddressProps> = props => {
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.phone} error={!!getFieldError(errors, "phone")}
fullWidth fullWidth
helperText={errors.phone} helperText={getFieldError(errors, "phone")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Phone" defaultMessage: "Phone"
})} })}

View file

@ -7,15 +7,13 @@ import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { SiteSettingsPageFormData } from "../SiteSettingsPage"; import { SiteSettingsPageFormData } from "../SiteSettingsPage";
interface SiteSettingsDetailsProps { interface SiteSettingsDetailsProps {
data: SiteSettingsPageFormData; data: SiteSettingsPageFormData;
errors: Partial<{ errors: UserError[];
description: string;
domain: string;
name: string;
}>;
disabled: boolean; disabled: boolean;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -36,14 +34,14 @@ const SiteSettingsDetails: React.FC<SiteSettingsDetailsProps> = ({
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "name")}
fullWidth fullWidth
name="name" name="name"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Name of your store" defaultMessage: "Name of your store"
})} })}
helperText={ helperText={
errors.name || getFieldError(errors, "name")?.message ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: defaultMessage:
"Name of your store is shown on tab in web browser" "Name of your store is shown on tab in web browser"
@ -55,27 +53,27 @@ const SiteSettingsDetails: React.FC<SiteSettingsDetailsProps> = ({
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.domain} error={!!getFieldError(errors, "domain")}
fullWidth fullWidth
name="domain" name="domain"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "URL of your online store" defaultMessage: "URL of your online store"
})} })}
helperText={errors.domain} helperText={getFieldError(errors, "domain")?.message}
value={data.domain} value={data.domain}
onChange={onChange} onChange={onChange}
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.domain} error={!!getFieldError(errors, "description")}
fullWidth fullWidth
name="description" name="description"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Store Description" defaultMessage: "Store Description"
})} })}
helperText={ helperText={
errors.description || getFieldError(errors, "description")?.message ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: defaultMessage:
"Store description is shown on taskbar after your store name" "Store description is shown on taskbar after your store name"

View file

@ -7,10 +7,12 @@ import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Form, { FormProps } from "@saleor/components/Form"; import Form from "@saleor/components/Form";
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 { buttonMessages } from "@saleor/intl"; import { buttonMessages } from "@saleor/intl";
import { UserError, DialogProps } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
import { authorizationKeyTypes } from "../../../misc"; import { authorizationKeyTypes } from "../../../misc";
import { AuthorizationKeyType } from "../../../types/globalTypes"; import { AuthorizationKeyType } from "../../../types/globalTypes";
@ -20,13 +22,10 @@ export interface SiteSettingsKeyDialogForm {
type: AuthorizationKeyType; type: AuthorizationKeyType;
} }
export interface SiteSettingsKeyDialogProps export interface SiteSettingsKeyDialogProps extends DialogProps {
extends Pick< errors: UserError[];
FormProps<SiteSettingsKeyDialogForm>, initial: SiteSettingsKeyDialogForm;
Exclude<keyof FormProps<SiteSettingsKeyDialogForm>, "children"> onSubmit: (data: SiteSettingsKeyDialogForm) => void;
> {
open: boolean;
onClose: () => void;
} }
const SiteSettingsKeyDialog: React.FC<SiteSettingsKeyDialogProps> = ({ const SiteSettingsKeyDialog: React.FC<SiteSettingsKeyDialogProps> = ({
@ -40,8 +39,8 @@ const SiteSettingsKeyDialog: React.FC<SiteSettingsKeyDialogProps> = ({
return ( return (
<Dialog onClose={onClose} maxWidth="xs" open={open}> <Dialog onClose={onClose} maxWidth="xs" open={open}>
<Form initial={initial} onSubmit={onSubmit} errors={errors}> <Form initial={initial} onSubmit={onSubmit}>
{({ change, data, errors }) => ( {({ change, data }) => (
<> <>
<DialogTitle> <DialogTitle>
<FormattedMessage <FormattedMessage
@ -55,37 +54,37 @@ const SiteSettingsKeyDialog: React.FC<SiteSettingsKeyDialogProps> = ({
label: authorizationKeyTypes[key], label: authorizationKeyTypes[key],
value: key value: key
}))} }))}
error={!!errors.keyType} error={!!getFieldError(errors, "keyType")}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Authentication type", defaultMessage: "Authentication type",
description: "authentication provider name" description: "authentication provider name"
})} })}
hint={errors.keyType} hint={getFieldError(errors, "keyType")?.message}
name="type" name="type"
onChange={change} onChange={change}
value={data.type} value={data.type}
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
error={!!errors.key} error={!!getFieldError(errors, "key")}
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Key", defaultMessage: "Key",
description: "authentication provider API key" description: "authentication provider API key"
})} })}
helperText={errors.key} helperText={getFieldError(errors, "key")?.message}
name="key" name="key"
onChange={change} onChange={change}
value={data.key} value={data.key}
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
error={!!errors.password} error={!!getFieldError(errors, "password")}
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Password" defaultMessage: "Password"
})} })}
helperText={errors.password} helperText={getFieldError(errors, "password")?.message}
name="password" name="password"
onChange={change} onChange={change}
value={data.password} value={data.password}

View file

@ -9,7 +9,8 @@ import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
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 { FormErrors } from "@saleor/types"; import { UserError } from "@saleor/types";
import { getFieldError } from "@saleor/utils/errors";
export interface SiteSettingsMailingFormData { export interface SiteSettingsMailingFormData {
defaultMailSenderName: string; defaultMailSenderName: string;
@ -18,11 +19,7 @@ export interface SiteSettingsMailingFormData {
} }
interface SiteSettingsMailingProps { interface SiteSettingsMailingProps {
data: SiteSettingsMailingFormData; data: SiteSettingsMailingFormData;
errors: FormErrors< errors: UserError[];
| "defaultMailSenderAddress"
| "defaultMailSenderName"
| "customerSetPasswordUrl"
>;
disabled: boolean; disabled: boolean;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
@ -68,27 +65,29 @@ const SiteSettingsMailing: React.FC<SiteSettingsMailingProps> = props => {
</Typography> </Typography>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.defaultMailSenderAddress} error={!!getFieldError(errors, "defaultMailSenderAddress")}
fullWidth fullWidth
name="defaultMailSenderAddress" name="defaultMailSenderAddress"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Mailing email address" defaultMessage: "Mailing email address"
})} })}
helperText={errors.defaultMailSenderAddress} helperText={
getFieldError(errors, "defaultMailSenderAddress")?.message
}
value={data.defaultMailSenderAddress} value={data.defaultMailSenderAddress}
onChange={onChange} onChange={onChange}
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.defaultMailSenderName} error={!!getFieldError(errors, "defaultMailSenderName")}
fullWidth fullWidth
name="defaultMailSenderName" name="defaultMailSenderName"
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Mailing email sender" defaultMessage: "Mailing email sender"
})} })}
helperText={ helperText={
errors.defaultMailSenderName || getFieldError(errors, "defaultMailSenderName")?.message ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: 'This will be visible as "from" name', defaultMessage: 'This will be visible as "from" name',
description: "email sender" description: "email sender"
@ -102,7 +101,7 @@ const SiteSettingsMailing: React.FC<SiteSettingsMailingProps> = props => {
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.customerSetPasswordUrl} error={!!getFieldError(errors, "customerSetPasswordUrl")}
fullWidth fullWidth
name="customerSetPasswordUrl" name="customerSetPasswordUrl"
label={intl.formatMessage({ label={intl.formatMessage({
@ -112,7 +111,7 @@ const SiteSettingsMailing: React.FC<SiteSettingsMailingProps> = props => {
defaultMessage: "URL address" defaultMessage: "URL address"
})} })}
helperText={ helperText={
errors.customerSetPasswordUrl || getFieldError(errors, "customerSetPasswordUrl")?.message ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: defaultMessage:
"This URL will be used as a main URL for password resets. It will be sent via email." "This URL will be used as a main URL for password resets. It will be sent via email."

View file

@ -127,9 +127,10 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
name: maybe(() => shop.name, "") name: maybe(() => shop.name, "")
}; };
const formErrors = [...errors, ...validationErrors];
return ( return (
<Form <Form
errors={[...errors, ...validationErrors]}
initial={initialForm} initial={initialForm}
onSubmit={data => { onSubmit={data => {
const submitFunc = areAddressInputFieldsModified(data) const submitFunc = areAddressInputFieldsModified(data)
@ -139,8 +140,7 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
}} }}
confirmLeave confirmLeave
> >
{({ change, data, errors: formErrors, hasChanged, submit }) => { {({ change, data, hasChanged, submit }) => {
const siteFormErrors = { ...formErrors };
const countryChoices = mapCountriesToChoices( const countryChoices = mapCountriesToChoices(
maybe(() => shop.countries, []) maybe(() => shop.countries, [])
); );
@ -169,7 +169,7 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
</div> </div>
<SiteSettingsDetails <SiteSettingsDetails
data={data} data={data}
errors={siteFormErrors} errors={formErrors}
disabled={disabled} disabled={disabled}
onChange={change} onChange={change}
/> />
@ -187,7 +187,7 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
</div> </div>
<SiteSettingsMailing <SiteSettingsMailing
data={data} data={data}
errors={siteFormErrors} errors={formErrors}
disabled={disabled} disabled={disabled}
onChange={change} onChange={change}
/> />
@ -208,7 +208,7 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
data={data} data={data}
displayCountry={displayCountry} displayCountry={displayCountry}
countries={countryChoices} countries={countryChoices}
errors={siteFormErrors} errors={formErrors}
disabled={disabled} disabled={disabled}
onChange={change} onChange={change}
onCountryChange={handleCountryChange} onCountryChange={handleCountryChange}

View file

@ -16,6 +16,8 @@ import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import { buttonMessages, commonMessages } from "@saleor/intl"; import { buttonMessages, commonMessages } from "@saleor/intl";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import { getFieldError } from "@saleor/utils/errors";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
export interface FormData { export interface FormData {
@ -65,13 +67,14 @@ interface StaffAddMemberDialogProps {
const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => { const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
const { confirmButtonState, errors, open, onClose, onConfirm } = props; const { confirmButtonState, errors, open, onClose, onConfirm } = props;
const classes = useStyles(props); const classes = useStyles(props);
const dialogErrors = useModalDialogErrors(errors, open);
const intl = useIntl(); const intl = useIntl();
return ( return (
<Dialog onClose={onClose} open={open}> <Dialog onClose={onClose} open={open}>
<Form errors={errors} initial={initialForm} onSubmit={onConfirm}> <Form initial={initialForm} onSubmit={onConfirm}>
{({ change, data, errors: formErrors, hasChanged }) => ( {({ change, data, hasChanged }) => (
<> <>
<DialogTitle> <DialogTitle>
<FormattedMessage <FormattedMessage
@ -82,8 +85,8 @@ const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
<DialogContent> <DialogContent>
<div className={classes.textFieldGrid}> <div className={classes.textFieldGrid}>
<TextField <TextField
error={!!formErrors.firstName} error={!!getFieldError(dialogErrors, "firstName")}
helperText={formErrors.firstName} helperText={getFieldError(dialogErrors, "firstName")?.message}
label={intl.formatMessage(commonMessages.firstName)} label={intl.formatMessage(commonMessages.firstName)}
name="firstName" name="firstName"
type="text" type="text"
@ -91,8 +94,8 @@ const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
onChange={change} onChange={change}
/> />
<TextField <TextField
error={!!formErrors.lastName} error={!!getFieldError(dialogErrors, "lastName")}
helperText={formErrors.lastName} helperText={getFieldError(dialogErrors, "lastName")?.message}
label={intl.formatMessage(commonMessages.lastName)} label={intl.formatMessage(commonMessages.lastName)}
name="lastName" name="lastName"
type="text" type="text"
@ -102,9 +105,9 @@ const StaffAddMemberDialog: React.FC<StaffAddMemberDialogProps> = props => {
</div> </div>
<FormSpacer /> <FormSpacer />
<TextField <TextField
error={!!formErrors.email} error={!!getFieldError(dialogErrors, "email")}
fullWidth fullWidth
helperText={formErrors.email} helperText={getFieldError(dialogErrors, "email")?.message}
label={intl.formatMessage(commonMessages.email)} label={intl.formatMessage(commonMessages.email)}
name="email" name="email"
type="email" type="email"

View file

@ -15,6 +15,7 @@ import ConfirmButton, {
} from "@saleor/components/ConfirmButton"; } from "@saleor/components/ConfirmButton";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import { getFieldError } from "@saleor/utils/errors";
interface StaffPasswordResetDialogFormData { interface StaffPasswordResetDialogFormData {
newPassword: string; newPassword: string;
@ -33,13 +34,13 @@ const initialForm: StaffPasswordResetDialogFormData = {
const StaffPasswordResetDialog: React.FC<StaffPasswordResetDialogProps> = ({ const StaffPasswordResetDialog: React.FC<StaffPasswordResetDialogProps> = ({
confirmButtonState, confirmButtonState,
errors: apiErrors, errors,
open, open,
onClose, onClose,
onSubmit onSubmit
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const dialogErrors = useModalDialogErrors(apiErrors, open); const dialogErrors = useModalDialogErrors(errors, open);
return ( return (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
@ -49,14 +50,14 @@ const StaffPasswordResetDialog: React.FC<StaffPasswordResetDialogProps> = ({
description="dialog header" description="dialog header"
/> />
</DialogTitle> </DialogTitle>
<Form errors={dialogErrors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors, submit }) => ( {({ change, data, submit }) => (
<> <>
<DialogContent> <DialogContent>
<TextField <TextField
error={!!errors.oldPassword} error={!!getFieldError(dialogErrors, "oldPassword")}
fullWidth fullWidth
helperText={errors.oldPassword} helperText={getFieldError(dialogErrors, "oldPassword")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Previous Password", defaultMessage: "Previous Password",
description: "input label" description: "input label"
@ -67,10 +68,10 @@ const StaffPasswordResetDialog: React.FC<StaffPasswordResetDialogProps> = ({
/> />
<FormSpacer /> <FormSpacer />
<TextField <TextField
error={!!errors.newPassword} error={!!getFieldError(dialogErrors, "newPassword")}
fullWidth fullWidth
helperText={ helperText={
errors.newPassword || getFieldError(dialogErrors, "newPassword") ||
intl.formatMessage({ intl.formatMessage({
defaultMessage: defaultMessage:
"New password must be at least 8 characters long" "New password must be at least 8 characters long"

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@ import React from "react";
import placeholderCollectionImage from "@assets/images/block1.jpg"; import placeholderCollectionImage from "@assets/images/block1.jpg";
import placeholderProductImage from "@assets/images/placeholder60x60.png"; import placeholderProductImage from "@assets/images/placeholder60x60.png";
import { formError } from "@saleor/storybook/misc";
import CollectionDetailsPage, { import CollectionDetailsPage, {
CollectionDetailsPageProps CollectionDetailsPageProps
} from "../../../collections/components/CollectionDetailsPage"; } from "../../../collections/components/CollectionDetailsPage";
@ -21,6 +22,7 @@ const props: Omit<CollectionDetailsPageProps, "classes"> = {
...pageListProps.default, ...pageListProps.default,
collection, collection,
disabled: false, disabled: false,
errors: [],
isFeatured: true, isFeatured: true,
onBack: () => undefined, onBack: () => undefined,
onCollectionRemove: () => undefined, onCollectionRemove: () => undefined,
@ -37,6 +39,14 @@ storiesOf("Views / Collections / Collection details", module)
.add("loading", () => ( .add("loading", () => (
<CollectionDetailsPage {...props} collection={undefined} disabled={true} /> <CollectionDetailsPage {...props} collection={undefined} disabled={true} />
)) ))
.add("form errors", () => (
<CollectionDetailsPage
{...props}
errors={["name", "descriptionJson", "publicationDate", "isPublished"].map(
formError
)}
/>
))
.add("no products", () => ( .add("no products", () => (
<CollectionDetailsPage <CollectionDetailsPage
{...props} {...props}

View file

@ -20,7 +20,7 @@ storiesOf("Generics / AddressEdit", module)
> >
<CardContent> <CardContent>
<AddressEdit <AddressEdit
errors={{}} errors={[]}
data={transformAddressToForm(customer.defaultBillingAddress)} data={transformAddressToForm(customer.defaultBillingAddress)}
countries={countries.map(c => ({ countries={countries.map(c => ({
label: c.label, label: c.label,

View file

@ -1,6 +1,7 @@
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import { formError } from "@saleor/storybook/misc";
import MenuCreateDialog, { import MenuCreateDialog, {
MenuCreateDialogProps MenuCreateDialogProps
} from "../../../navigation/components/MenuCreateDialog"; } from "../../../navigation/components/MenuCreateDialog";
@ -9,6 +10,7 @@ import Decorator from "../../Decorator";
const props: MenuCreateDialogProps = { const props: MenuCreateDialogProps = {
confirmButtonState: "default", confirmButtonState: "default",
disabled: false, disabled: false,
errors: [],
onClose: () => undefined, onClose: () => undefined,
onConfirm: () => undefined, onConfirm: () => undefined,
open: true open: true
@ -19,4 +21,7 @@ storiesOf("Navigation / Menu create", module)
.add("default", () => <MenuCreateDialog {...props} />) .add("default", () => <MenuCreateDialog {...props} />)
.add("loading", () => ( .add("loading", () => (
<MenuCreateDialog {...props} disabled={true} confirmButtonState="loading" /> <MenuCreateDialog {...props} disabled={true} confirmButtonState="loading" />
))
.add("form errors", () => (
<MenuCreateDialog {...props} errors={["name"].map(formError)} />
)); ));

View file

@ -1,6 +1,7 @@
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
import { formError } from "@saleor/storybook/misc";
import ProductTypeAttributeEditDialog, { import ProductTypeAttributeEditDialog, {
ProductTypeAttributeEditDialogProps ProductTypeAttributeEditDialogProps
} from "../../../productTypes/components/ProductTypeAttributeEditDialog"; } from "../../../productTypes/components/ProductTypeAttributeEditDialog";
@ -32,10 +33,6 @@ storiesOf("Product types / Edit attribute", module)
.add("form errors", () => ( .add("form errors", () => (
<ProductTypeAttributeEditDialog <ProductTypeAttributeEditDialog
{...props} {...props}
// errors={["name", "values"].map(field => formError(field))} errors={["name", "values"].map(field => formError(field))}
errors={["name", "values"].map(field => ({
field,
message: "Generic error"
}))}
/> />
)); ));

View file

@ -16,6 +16,7 @@ const props: SiteSettingsKeyDialogProps = {
type: AuthorizationKeyType.FACEBOOK type: AuthorizationKeyType.FACEBOOK
}, },
onClose: () => undefined, onClose: () => undefined,
onSubmit: () => undefined,
open: true open: true
}; };

View file

@ -6,7 +6,7 @@ import { IFilter } from "./components/Filter";
import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField"; import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField";
export interface UserError { export interface UserError {
field: string; field: string | null;
message: string; message: string;
} }
@ -116,8 +116,6 @@ export interface Node {
id: string; id: string;
} }
export type FormErrors<TKeys extends string> = Partial<Record<TKeys, string>>;
export type Pagination = Partial<{ export type Pagination = Partial<{
after: string; after: string;
before: string; before: string;

View file

@ -1,10 +1,7 @@
import { maybe } from "@saleor/misc";
import { UserError } from "@saleor/types"; import { UserError } from "@saleor/types";
export function getFieldError(errors: UserError[], field: string): string { export function getFieldError(errors: UserError[], field: string): UserError {
const err = errors.find(err => err.field === field); return errors.find(err => err.field === field);
return maybe(() => err.message);
} }
export function getErrors(errors: UserError[]): string[] { export function getErrors(errors: UserError[]): string[] {

View file

@ -40,7 +40,7 @@ export interface WebhookCreatePageProps {
const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({ const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({
disabled, disabled,
errors: apiErrors, errors,
saveButtonBarState, saveButtonBarState,
services, services,
fetchServiceAccounts, fetchServiceAccounts,
@ -70,8 +70,8 @@ const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({
); );
return ( return (
<Form errors={apiErrors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ data, errors, hasChanged, submit, change }) => { {({ data, hasChanged, submit, change }) => {
const handleServiceSelect = createSingleAutocompleteSelectHandler( const handleServiceSelect = createSingleAutocompleteSelectHandler(
change, change,
setSelectedServiceAcccount, setSelectedServiceAcccount,
@ -93,11 +93,10 @@ const WebhookCreatePage: React.FC<WebhookCreatePageProps> = ({
<WebhookInfo <WebhookInfo
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={errors}
fetchServiceAccounts={fetchServiceAccounts}
serviceDisplayValue={selectedServiceAcccount} serviceDisplayValue={selectedServiceAcccount}
services={servicesChoiceList} services={servicesChoiceList}
fetchServiceAccounts={fetchServiceAccounts}
apiErrors={apiErrors}
errors={errors}
serviceOnChange={handleServiceSelect} serviceOnChange={handleServiceSelect}
onChange={change} onChange={change}
/> />

View file

@ -14,17 +14,16 @@ import SingleAutocompleteSelectField, {
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import { ChangeEvent } from "@saleor/hooks/useForm"; import { ChangeEvent } from "@saleor/hooks/useForm";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "@saleor/types";
import { WebhookCreate_webhookCreate_webhookErrors } from "@saleor/webhooks/types/WebhookCreate"; import { WebhookCreate_webhookCreate_webhookErrors } from "@saleor/webhooks/types/WebhookCreate";
import { getFieldError } from "@saleor/utils/errors";
import { FormData } from "../WebhooksDetailsPage"; import { FormData } from "../WebhooksDetailsPage";
interface WebhookInfoProps { interface WebhookInfoProps {
apiErrors: WebhookCreate_webhookCreate_webhookErrors[];
data: FormData; data: FormData;
disabled: boolean; disabled: boolean;
errors: WebhookCreate_webhookCreate_webhookErrors[];
serviceDisplayValue: string; serviceDisplayValue: string;
services: SingleAutocompleteChoiceType[]; services: SingleAutocompleteChoiceType[];
errors: FormErrors<"name" | "targetUrl" | "secretKey">;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
serviceOnChange: (event: ChangeEvent) => void; serviceOnChange: (event: ChangeEvent) => void;
fetchServiceAccounts: (data: string) => void; fetchServiceAccounts: (data: string) => void;
@ -45,7 +44,6 @@ const useStyles = makeStyles(
); );
const WebhookInfo: React.FC<WebhookInfoProps> = ({ const WebhookInfo: React.FC<WebhookInfoProps> = ({
apiErrors,
data, data,
disabled, disabled,
services, services,
@ -58,7 +56,7 @@ const WebhookInfo: React.FC<WebhookInfoProps> = ({
const classes = useStyles({}); const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
const serviceAccountsError = const serviceAccountsError =
apiErrors.filter(error => error.field === null).length > 0; errors.filter(error => error.field === null).length > 0;
return ( return (
<Card> <Card>
@ -74,8 +72,8 @@ const WebhookInfo: React.FC<WebhookInfoProps> = ({
</Typography> </Typography>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!getFieldError(errors, "name")}
helperText={errors.name} helperText={getFieldError(errors, "name")?.message}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Webhook Name", defaultMessage: "Webhook Name",
description: "webhook" description: "webhook"
@ -117,11 +115,14 @@ const WebhookInfo: React.FC<WebhookInfoProps> = ({
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.targetUrl} error={!!getFieldError(errors, "targetUrl")}
helperText={intl.formatMessage({ helperText={
defaultMessage: "This URL will receive webhook POST requests", getFieldError(errors, "targetUrl")?.message ||
description: "webhook target url help text" intl.formatMessage({
})} defaultMessage: "This URL will receive webhook POST requests",
description: "webhook target url help text"
})
}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Target URL", defaultMessage: "Target URL",
description: "webhook" description: "webhook"
@ -134,12 +135,15 @@ const WebhookInfo: React.FC<WebhookInfoProps> = ({
<FormSpacer /> <FormSpacer />
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.secretKey} error={!!getFieldError(errors, "secretKey")}
helperText={intl.formatMessage({ helperText={
defaultMessage: getFieldError(errors, "secretKey")?.message ||
"secret key is used to create a hash signature with each payload. *optional field", intl.formatMessage({
description: "webhook secret key help text" defaultMessage:
})} "secret key is used to create a hash signature with each payload. *optional field",
description: "webhook secret key help text"
})
}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Secrect Key", defaultMessage: "Secrect Key",
description: "webhook" description: "webhook"

View file

@ -45,7 +45,7 @@ export interface WebhooksDetailsPageProps {
const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({ const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({
disabled, disabled,
errors: apiErrors, errors,
webhook, webhook,
saveButtonBarState, saveButtonBarState,
services, services,
@ -81,8 +81,8 @@ const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({
[] []
); );
return ( return (
<Form errors={apiErrors} initial={initialForm} onSubmit={onSubmit}> <Form initial={initialForm} onSubmit={onSubmit}>
{({ data, errors, hasChanged, submit, change }) => { {({ data, hasChanged, submit, change }) => {
const handleServiceSelect = createSingleAutocompleteSelectHandler( const handleServiceSelect = createSingleAutocompleteSelectHandler(
change, change,
setSelectedServiceAcccounts, setSelectedServiceAcccounts,
@ -107,7 +107,6 @@ const WebhooksDetailsPage: React.FC<WebhooksDetailsPageProps> = ({
<Grid> <Grid>
<div> <div>
<WebhookInfo <WebhookInfo
apiErrors={apiErrors}
data={data} data={data}
disabled={disabled} disabled={disabled}
serviceDisplayValue={selectedServiceAcccounts} serviceDisplayValue={selectedServiceAcccounts}