add error handling to seo form
This commit is contained in:
parent
8093876c7b
commit
828e207209
6 changed files with 79 additions and 12 deletions
|
@ -79,6 +79,7 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
isCreating={true}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage({
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Add search engine title and description to make this category easier to find"
|
"Add search engine title and description to make this category easier to find"
|
||||||
|
|
|
@ -135,6 +135,7 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
isCreating={true}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
descriptionPlaceholder=""
|
descriptionPlaceholder=""
|
||||||
|
|
|
@ -4,6 +4,11 @@ import CardContent from "@material-ui/core/CardContent";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { PageErrorFragment } from "@saleor/fragments/types/PageErrorFragment";
|
||||||
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
|
import { PageErrorCode, ProductErrorCode } from "@saleor/types/globalTypes";
|
||||||
|
import { getProductErrorMessage } from "@saleor/utils/errors";
|
||||||
|
import getPageErrorMessage from "@saleor/utils/errors/page";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
@ -12,6 +17,21 @@ import slugify from "slugify";
|
||||||
import CardTitle from "../CardTitle";
|
import CardTitle from "../CardTitle";
|
||||||
import FormSpacer from "../FormSpacer";
|
import FormSpacer from "../FormSpacer";
|
||||||
|
|
||||||
|
enum SeoField {
|
||||||
|
slug = "slug",
|
||||||
|
title = "seoTitle",
|
||||||
|
description = "seoDescription"
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_INPUT_MESSAGE =
|
||||||
|
"If empty, the preview shows what will be autogenerated.";
|
||||||
|
|
||||||
|
interface Error {
|
||||||
|
__typename: "ProductError" | "PageError";
|
||||||
|
field: string | null;
|
||||||
|
code: ProductErrorCode | PageErrorCode;
|
||||||
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
addressBar: {
|
addressBar: {
|
||||||
|
@ -66,8 +86,10 @@ interface SeoFormProps {
|
||||||
description?: string;
|
description?: string;
|
||||||
descriptionPlaceholder: string;
|
descriptionPlaceholder: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
errors?: ProductErrorFragment[] | PageErrorFragment[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
|
isCreating?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
slugPlaceholder?: string;
|
slugPlaceholder?: string;
|
||||||
|
@ -81,7 +103,9 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
description,
|
description,
|
||||||
descriptionPlaceholder,
|
descriptionPlaceholder,
|
||||||
disabled,
|
disabled,
|
||||||
|
errors,
|
||||||
helperText,
|
helperText,
|
||||||
|
isCreating = false,
|
||||||
loading,
|
loading,
|
||||||
title,
|
title,
|
||||||
slug,
|
slug,
|
||||||
|
@ -96,6 +120,43 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
const toggleExpansion = () => setExpansionStatus(!expanded);
|
const toggleExpansion = () => setExpansionStatus(!expanded);
|
||||||
const shouldDisplayHelperText = helperText && !expanded;
|
const shouldDisplayHelperText = helperText && !expanded;
|
||||||
|
|
||||||
|
const getFilteredErrors = () =>
|
||||||
|
(errors as Error[])?.filter(({ field }) =>
|
||||||
|
Object.keys(SeoField).includes(field)
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const getError = (fieldName: SeoField) =>
|
||||||
|
getFilteredErrors().find(({ field }) => fieldName === field);
|
||||||
|
|
||||||
|
const isError = (fieldName: SeoField) => !!getError(fieldName);
|
||||||
|
|
||||||
|
const getSlugHelperText = () => {
|
||||||
|
const error = isError(SeoField.slug);
|
||||||
|
|
||||||
|
if (isCreating && !error) {
|
||||||
|
return intl.formatMessage({
|
||||||
|
defaultMessage: DEFAULT_INPUT_MESSAGE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return getSlugErrorHelperText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSlugErrorHelperText = () => {
|
||||||
|
const error = getError(SeoField.slug);
|
||||||
|
const { __typename: type } = error;
|
||||||
|
|
||||||
|
return type === "ProductError"
|
||||||
|
? getProductErrorMessage(error as ProductErrorFragment, intl)
|
||||||
|
: getPageErrorMessage(error as PageErrorFragment, intl);
|
||||||
|
};
|
||||||
|
|
||||||
|
// .replace(/[^\x00-\x7F]/g, "")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
|
@ -122,7 +183,8 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<TextField
|
<TextField
|
||||||
name="slug"
|
error={isError(SeoField.slug)}
|
||||||
|
name={SeoField.slug}
|
||||||
label={
|
label={
|
||||||
<div className={classes.labelContainer}>
|
<div className={classes.labelContainer}>
|
||||||
<div className={classes.label}>
|
<div className={classes.label}>
|
||||||
|
@ -142,10 +204,7 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
helperText={intl.formatMessage({
|
helperText={getSlugHelperText()}
|
||||||
defaultMessage:
|
|
||||||
"If empty, the preview shows what will be autogenerated."
|
|
||||||
})}
|
|
||||||
value={slug?.slice(0, 69)}
|
value={slug?.slice(0, 69)}
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
placeholder={slug || slugify(slugPlaceholder, { lower: true })}
|
placeholder={slug || slugify(slugPlaceholder, { lower: true })}
|
||||||
|
@ -154,7 +213,8 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<TextField
|
<TextField
|
||||||
name="seoTitle"
|
name={SeoField.title}
|
||||||
|
error={isError(SeoField.title)}
|
||||||
label={
|
label={
|
||||||
<div className={classes.labelContainer}>
|
<div className={classes.labelContainer}>
|
||||||
<div className={classes.label}>
|
<div className={classes.label}>
|
||||||
|
@ -175,8 +235,7 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage({
|
||||||
defaultMessage:
|
defaultMessage: DEFAULT_INPUT_MESSAGE
|
||||||
"If empty, the preview shows what will be autogenerated."
|
|
||||||
})}
|
})}
|
||||||
value={title?.slice(0, 69)}
|
value={title?.slice(0, 69)}
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
|
@ -186,7 +245,8 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<TextField
|
<TextField
|
||||||
name="seoDescription"
|
error={isError(SeoField.description)}
|
||||||
|
name={SeoField.description}
|
||||||
label={
|
label={
|
||||||
<div className={classes.labelContainer}>
|
<div className={classes.labelContainer}>
|
||||||
<div className={classes.label}>
|
<div className={classes.label}>
|
||||||
|
@ -207,8 +267,7 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage({
|
||||||
defaultMessage:
|
defaultMessage: DEFAULT_INPUT_MESSAGE
|
||||||
"If empty, the preview shows what will be autogenerated."
|
|
||||||
})}
|
})}
|
||||||
value={description?.slice(0, 299)}
|
value={description?.slice(0, 299)}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
|
@ -38,6 +38,7 @@ export interface PageDetailsPageProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: PageErrorFragment[];
|
errors: PageErrorFragment[];
|
||||||
page: PageDetails_page;
|
page: PageDetails_page;
|
||||||
|
isCreating?: boolean;
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
|
@ -55,6 +56,7 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const localizeDate = useDateLocalize();
|
const localizeDate = useDateLocalize();
|
||||||
|
const pageExists = page === null;
|
||||||
|
|
||||||
const initialForm: FormData = {
|
const initialForm: FormData = {
|
||||||
content: maybe(
|
content: maybe(
|
||||||
|
@ -77,7 +79,7 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={
|
title={
|
||||||
page === null
|
pageExists
|
||||||
? intl.formatMessage({
|
? intl.formatMessage({
|
||||||
defaultMessage: "Create Page",
|
defaultMessage: "Create Page",
|
||||||
description: "page header"
|
description: "page header"
|
||||||
|
@ -96,6 +98,8 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
errors={errors}
|
||||||
|
isCreating={pageExists}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
descriptionPlaceholder={maybe(
|
descriptionPlaceholder={maybe(
|
||||||
|
|
|
@ -306,6 +306,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
isCreating={true}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage({
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Add search engine title and description to make this product easier to find"
|
"Add search engine title and description to make this product easier to find"
|
||||||
|
|
|
@ -352,6 +352,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
)}
|
)}
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
errors={errors}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={data.name}
|
titlePlaceholder={data.name}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
|
|
Loading…
Reference in a new issue