Use error codes in navigation section

This commit is contained in:
dominik-zeglen 2020-03-11 14:03:31 +01:00
parent 5146386ee5
commit 61c3eb51fd
9 changed files with 95 additions and 22 deletions

View file

@ -12,8 +12,9 @@ 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 { MenuErrorFragment } from "@saleor/navigation/types/MenuErrorFragment";
import { UserError } from "@saleor/types"; import { getFormErrors } from "@saleor/utils/errors";
import getMenuErrorMessage from "@saleor/utils/errors/menu";
export interface MenuCreateDialogFormData { export interface MenuCreateDialogFormData {
name: string; name: string;
@ -22,7 +23,7 @@ export interface MenuCreateDialogFormData {
export interface MenuCreateDialogProps { export interface MenuCreateDialogProps {
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: MenuErrorFragment[];
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
onConfirm: (data: MenuCreateDialogFormData) => void; onConfirm: (data: MenuCreateDialogFormData) => void;
@ -42,6 +43,8 @@ const MenuCreateDialog: React.FC<MenuCreateDialogProps> = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["name"], errors);
return ( return (
<Dialog onClose={onClose} maxWidth="sm" fullWidth open={open}> <Dialog onClose={onClose} maxWidth="sm" fullWidth open={open}>
<DialogTitle> <DialogTitle>
@ -57,9 +60,9 @@ const MenuCreateDialog: React.FC<MenuCreateDialogProps> = ({
<DialogContent> <DialogContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!getFieldError(errors, "name")} error={!!formErrors.name}
fullWidth fullWidth
helperText={getFieldError(errors, "name")?.message} helperText={getMenuErrorMessage(formErrors.name, intl)}
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Menu Title", defaultMessage: "Menu Title",
id: "menuCreateDialogMenuTitleLabel" id: "menuCreateDialogMenuTitleLabel"

View file

@ -10,6 +10,7 @@ import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { MenuErrorFragment } from "@saleor/navigation/types/MenuErrorFragment";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { MenuDetails_menu } from "../../types/MenuDetails"; import { MenuDetails_menu } from "../../types/MenuDetails";
import { MenuItemType } from "../MenuItemDialog"; import { MenuItemType } from "../MenuItemDialog";
@ -28,6 +29,7 @@ export interface MenuDetailsSubmitData extends MenuDetailsFormData {
export interface MenuDetailsPageProps { export interface MenuDetailsPageProps {
saveButtonState: ConfirmButtonTransitionState; saveButtonState: ConfirmButtonTransitionState;
disabled: boolean; disabled: boolean;
errors: MenuErrorFragment[];
menu: MenuDetails_menu; menu: MenuDetails_menu;
onBack: () => void; onBack: () => void;
onDelete: () => void; onDelete: () => void;
@ -39,6 +41,7 @@ export interface MenuDetailsPageProps {
const MenuDetailsPage: React.FC<MenuDetailsPageProps> = ({ const MenuDetailsPage: React.FC<MenuDetailsPageProps> = ({
disabled, disabled,
errors,
menu, menu,
saveButtonState, saveButtonState,
onBack, onBack,
@ -98,6 +101,7 @@ const MenuDetailsPage: React.FC<MenuDetailsPageProps> = ({
<MenuProperties <MenuProperties
data={data} data={data}
disabled={disabled} disabled={disabled}
errors={errors}
onChange={change} onChange={change}
/> />
<CardSpacer /> <CardSpacer />

View file

@ -21,9 +21,10 @@ import { buttonMessages, sectionNames } from "@saleor/intl";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories"; import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections"; import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { UserError } from "@saleor/types";
import { getErrors, getFieldError } from "@saleor/utils/errors";
import { getMenuItemByValue, IMenu } from "@saleor/utils/menu"; import { getMenuItemByValue, IMenu } from "@saleor/utils/menu";
import { MenuErrorFragment } from "@saleor/navigation/types/MenuErrorFragment";
import { getFieldError, getFormErrors } from "@saleor/utils/errors";
import getMenuErrorMessage from "@saleor/utils/errors/menu";
export type MenuItemType = "category" | "collection" | "link" | "page"; export type MenuItemType = "category" | "collection" | "link" | "page";
export interface MenuItemData { export interface MenuItemData {
@ -38,7 +39,7 @@ export interface MenuItemDialogFormData extends MenuItemData {
export interface MenuItemDialogProps { export interface MenuItemDialogProps {
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: MenuErrorFragment[];
initial?: MenuItemDialogFormData; initial?: MenuItemDialogFormData;
initialDisplayValue?: string; initialDisplayValue?: string;
loading: boolean; loading: boolean;
@ -112,7 +113,11 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
initialDisplayValue initialDisplayValue
]); ]);
const mutationErrors = getErrors(errors); const mutationErrors = errors.filter(err => err.field === null);
const formErrors = getFormErrors(["name"], errors);
const idError = ["category", "collection", "page", "url"]
.map(field => getFieldError(errors, field))
.reduce((acc, err) => acc || err);
let options: IMenu = []; let options: IMenu = [];
@ -208,10 +213,6 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
const handleSubmit = () => onSubmit(data); const handleSubmit = () => onSubmit(data);
const idError = ["category", "collection", "page", "url"]
.map(field => getFieldError(errors, field))
.reduce((acc, err) => acc || err);
return ( return (
<Dialog <Dialog
onClose={onClose} onClose={onClose}
@ -252,8 +253,8 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
})) }))
} }
name="name" name="name"
error={!!getFieldError(errors, "name")} error={!!formErrors.name}
helperText={getFieldError(errors, "name")?.message} helperText={getMenuErrorMessage(formErrors.name, intl)}
/> />
<FormSpacer /> <FormSpacer />
<AutocompleteSelectMenu <AutocompleteSelectMenu
@ -269,7 +270,7 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
loading={loading} loading={loading}
options={options} options={options}
error={!!idError} error={!!idError}
helperText={idError?.message} helperText={getMenuErrorMessage(idError, intl)}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
defaultMessage: "Start typing to begin search...", defaultMessage: "Start typing to begin search...",
id: "menuItemDialogLinkPlaceholder" id: "menuItemDialogLinkPlaceholder"
@ -280,8 +281,8 @@ const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
<> <>
<FormSpacer /> <FormSpacer />
{mutationErrors.map(err => ( {mutationErrors.map(err => (
<Typography key={err} color="error"> <Typography key={err.code} color="error">
{err} {getMenuErrorMessage(err, intl)}
</Typography> </Typography>
))} ))}
</> </>

View file

@ -6,21 +6,28 @@ 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 { MenuErrorFragment } from "@saleor/navigation/types/MenuErrorFragment";
import { getFormErrors } from "@saleor/utils/errors";
import getMenuErrorMessage from "@saleor/utils/errors/menu";
import { MenuDetailsFormData } from "../MenuDetailsPage"; import { MenuDetailsFormData } from "../MenuDetailsPage";
export interface MenuPropertiesProps { export interface MenuPropertiesProps {
data: MenuDetailsFormData; data: MenuDetailsFormData;
disabled: boolean; disabled: boolean;
errors: MenuErrorFragment[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
const MenuProperties: React.FC<MenuPropertiesProps> = ({ const MenuProperties: React.FC<MenuPropertiesProps> = ({
data, data,
disabled, disabled,
errors,
onChange onChange
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormErrors(["name"], errors);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
@ -29,12 +36,14 @@ const MenuProperties: React.FC<MenuPropertiesProps> = ({
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!formErrors.name}
name={"name" as keyof MenuDetailsFormData} name={"name" as keyof MenuDetailsFormData}
fullWidth fullWidth
label={intl.formatMessage({ label={intl.formatMessage({
defaultMessage: "Menu Title", defaultMessage: "Menu Title",
id: "menuPropertiesMenuTitle" id: "menuPropertiesMenuTitle"
})} })}
helperText={getMenuErrorMessage(formErrors.name, intl)}
value={data.name} value={data.name}
onChange={onChange} onChange={onChange}
/> />

View file

@ -169,6 +169,11 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
<> <>
<MenuDetailsPage <MenuDetailsPage
disabled={loading} disabled={loading}
errors={[
...(menuUpdateOpts.data?.menuUpdate.errors || []),
...(menuUpdateOpts.data?.menuItemMove.errors || []),
...(menuUpdateOpts.data?.menuUpdate.errors || [])
]}
menu={maybe(() => data.menu)} menu={maybe(() => data.menu)}
onBack={() => navigate(menuListUrl())} onBack={() => navigate(menuListUrl())}
onDelete={() => onDelete={() =>

View file

@ -1,7 +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 { MenuErrorCode } from "@saleor/types/globalTypes";
import MenuCreateDialog, { import MenuCreateDialog, {
MenuCreateDialogProps MenuCreateDialogProps
} from "../../../navigation/components/MenuCreateDialog"; } from "../../../navigation/components/MenuCreateDialog";
@ -23,5 +23,12 @@ storiesOf("Navigation / Menu create", module)
<MenuCreateDialog {...props} disabled={true} confirmButtonState="loading" /> <MenuCreateDialog {...props} disabled={true} confirmButtonState="loading" />
)) ))
.add("form errors", () => ( .add("form errors", () => (
<MenuCreateDialog {...props} errors={["name"].map(formError)} /> <MenuCreateDialog
{...props}
errors={["name"].map(field => ({
__typename: "MenuError",
code: MenuErrorCode.INVALID,
field
}))}
/>
)); ));

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 { MenuErrorCode } from "@saleor/types/globalTypes";
import MenuDetailsPage, { import MenuDetailsPage, {
MenuDetailsPageProps MenuDetailsPageProps
} from "../../../navigation/components/MenuDetailsPage"; } from "../../../navigation/components/MenuDetailsPage";
@ -9,6 +10,7 @@ import Decorator from "../../Decorator";
const props: MenuDetailsPageProps = { const props: MenuDetailsPageProps = {
disabled: false, disabled: false,
errors: [],
menu, menu,
onBack: () => undefined, onBack: () => undefined,
onDelete: () => undefined, onDelete: () => undefined,
@ -33,4 +35,14 @@ storiesOf("Views / Navigation / Menu details", module)
items: [] items: []
}} }}
/> />
))
.add("form errors", () => (
<MenuDetailsPage
{...props}
errors={["name"].map(field => ({
__typename: "MenuError",
code: MenuErrorCode.INVALID,
field
}))}
/>
)); ));

View file

@ -1,7 +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 { MenuErrorCode } from "@saleor/types/globalTypes";
import MenuItemDialog, { import MenuItemDialog, {
MenuItemDialogProps MenuItemDialogProps
} from "../../../navigation/components/MenuItemDialog"; } from "../../../navigation/components/MenuItemDialog";
@ -48,6 +48,10 @@ storiesOf("Navigation / Menu item", module)
.add("errors", () => ( .add("errors", () => (
<MenuItemDialog <MenuItemDialog
{...props} {...props}
errors={["", "", "name", "category"].map(formError)} errors={[null, null, "name", "category"].map(field => ({
__typename: "MenuError",
code: MenuErrorCode.INVALID,
field
}))}
/> />
)); ));

28
src/utils/errors/menu.ts Normal file
View file

@ -0,0 +1,28 @@
import { IntlShape } from "react-intl";
import { MenuErrorCode } from "@saleor/types/globalTypes";
import { commonMessages } from "@saleor/intl";
import { MenuErrorFragment } from "@saleor/navigation/types/MenuErrorFragment";
import commonErrorMessages from "./common";
function getMenuErrorMessage(
err: Omit<MenuErrorFragment, "__typename"> | undefined,
intl: IntlShape
): string {
if (err) {
switch (err.code) {
case MenuErrorCode.GRAPHQL_ERROR:
return intl.formatMessage(commonErrorMessages.graphqlError);
case MenuErrorCode.REQUIRED:
return intl.formatMessage(commonMessages.requiredField);
case MenuErrorCode.INVALID:
return intl.formatMessage(commonErrorMessages.invalid);
default:
return intl.formatMessage(commonErrorMessages.unknownError);
}
}
return undefined;
}
export default getMenuErrorMessage;