Merge pull request #720 from mirumee/feat/add-slug-to-seo-form
Add slug field to category, product, collection & page details
This commit is contained in:
commit
7e24e4a14f
45 changed files with 216 additions and 316 deletions
|
@ -42,11 +42,13 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Fix return to previous page on screen size change - #710 by @orzechdev
|
- Fix return to previous page on screen size change - #710 by @orzechdev
|
||||||
- Add variants reordering possibility - #716 by @orzechdev
|
- Add variants reordering possibility - #716 by @orzechdev
|
||||||
- Fix avatar change button - #719 by @orzechdev
|
- Fix avatar change button - #719 by @orzechdev
|
||||||
|
- Add slug field to product, collection, category & page details (update and create) - #720 by @mmarkusik
|
||||||
- Allow product variant to be set as default - #721 by @tomaszszymanski129
|
- Allow product variant to be set as default - #721 by @tomaszszymanski129
|
||||||
- Change plural form of "informations" to "information" strings across the app #722 by @mmarkusik
|
- Change plural form of "informations" to "information" strings across the app #722 by @mmarkusik
|
||||||
- Fix misaligned rich text draft controls - #725 by @orzechdev
|
- Fix misaligned rich text draft controls - #725 by @orzechdev
|
||||||
- Allow taxes to be configured per product - #728 by @dominik-zeglen
|
- Allow taxes to be configured per product - #728 by @dominik-zeglen
|
||||||
|
|
||||||
|
|
||||||
## 2.10.1
|
## 2.10.1
|
||||||
|
|
||||||
- Add weight field and fix warehouse country selection - #597 by @dominik-zeglen
|
- Add weight field and fix warehouse country selection - #597 by @dominik-zeglen
|
||||||
|
|
|
@ -1827,9 +1827,6 @@
|
||||||
"src_dot_components_dot_SeoForm_dot_1991321627": {
|
"src_dot_components_dot_SeoForm_dot_1991321627": {
|
||||||
"string": "Search engine description"
|
"string": "Search engine description"
|
||||||
},
|
},
|
||||||
"src_dot_components_dot_SeoForm_dot_2378618579": {
|
|
||||||
"string": "If empty, the preview shows what will be autogenerated."
|
|
||||||
},
|
|
||||||
"src_dot_components_dot_SeoForm_dot_3198271020": {
|
"src_dot_components_dot_SeoForm_dot_3198271020": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Edit website SEO"
|
"string": "Edit website SEO"
|
||||||
|
@ -1837,10 +1834,16 @@
|
||||||
"src_dot_components_dot_SeoForm_dot_3468022343": {
|
"src_dot_components_dot_SeoForm_dot_3468022343": {
|
||||||
"string": "Search Engine Preview"
|
"string": "Search Engine Preview"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_SeoForm_dot_3478065224": {
|
||||||
|
"string": "Slug"
|
||||||
|
},
|
||||||
"src_dot_components_dot_SeoForm_dot_3877274856": {
|
"src_dot_components_dot_SeoForm_dot_3877274856": {
|
||||||
"context": "character limit",
|
"context": "character limit",
|
||||||
"string": "{numberOfCharacters} of {maxCharacters} characters"
|
"string": "{numberOfCharacters} of {maxCharacters} characters"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_SeoForm_dot_seoFieldMessage": {
|
||||||
|
"string": "If empty, the preview shows what will be autogenerated."
|
||||||
|
},
|
||||||
"src_dot_components_dot_SingleAutocompleteSelectField_dot_1477537381": {
|
"src_dot_components_dot_SingleAutocompleteSelectField_dot_1477537381": {
|
||||||
"context": "add custom select input option",
|
"context": "add custom select input option",
|
||||||
"string": "Add new value: {value}"
|
"string": "Add new value: {value}"
|
||||||
|
@ -3567,16 +3570,6 @@
|
||||||
"context": "page status",
|
"context": "page status",
|
||||||
"string": "Not Published"
|
"string": "Not Published"
|
||||||
},
|
},
|
||||||
"src_dot_pages_dot_components_dot_PageSlug_dot_1324178587": {
|
|
||||||
"string": "URL"
|
|
||||||
},
|
|
||||||
"src_dot_pages_dot_components_dot_PageSlug_dot_3478065224": {
|
|
||||||
"context": "page internal name",
|
|
||||||
"string": "Slug"
|
|
||||||
},
|
|
||||||
"src_dot_pages_dot_components_dot_PageSlug_dot_4210828158": {
|
|
||||||
"string": "If empty, URL will be autogenerated from Page Name"
|
|
||||||
},
|
|
||||||
"src_dot_pages_dot_views_dot_1068617485": {
|
"src_dot_pages_dot_views_dot_1068617485": {
|
||||||
"context": "header",
|
"context": "header",
|
||||||
"string": "Create Page"
|
"string": "Create Page"
|
||||||
|
@ -5711,6 +5704,9 @@
|
||||||
"src_dot_utils_dot_errors_dot_misconfigured": {
|
"src_dot_utils_dot_errors_dot_misconfigured": {
|
||||||
"string": "Plugin is misconfigured and cannot be activated"
|
"string": "Plugin is misconfigured and cannot be activated"
|
||||||
},
|
},
|
||||||
|
"src_dot_utils_dot_errors_dot_nameAlreadyTaken": {
|
||||||
|
"string": "This name is already taken. Please provide another."
|
||||||
|
},
|
||||||
"src_dot_utils_dot_errors_dot_noShippingAddress": {
|
"src_dot_utils_dot_errors_dot_noShippingAddress": {
|
||||||
"context": "error message",
|
"context": "error message",
|
||||||
"string": "Cannot choose a shipping method for an order without the shipping address"
|
"string": "Cannot choose a shipping method for an order without the shipping address"
|
||||||
|
|
|
@ -19,6 +19,7 @@ import CategoryDetailsForm from "../../components/CategoryDetailsForm";
|
||||||
export interface FormData extends MetadataFormData {
|
export interface FormData extends MetadataFormData {
|
||||||
description: RawDraftContentState;
|
description: RawDraftContentState;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
seoTitle: string;
|
seoTitle: string;
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +30,8 @@ const initialData: FormData = {
|
||||||
name: "",
|
name: "",
|
||||||
privateMetadata: [],
|
privateMetadata: [],
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: ""
|
seoTitle: "",
|
||||||
|
slug: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface CategoryCreatePageProps {
|
export interface CategoryCreatePageProps {
|
||||||
|
@ -77,10 +79,13 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
allowEmptySlug={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"
|
||||||
})}
|
})}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.name}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={data.name}
|
titlePlaceholder={data.name}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export interface FormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
description: RawDraftContentState;
|
description: RawDraftContentState;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
seoTitle: string;
|
seoTitle: string;
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +119,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
name: category.name || "",
|
name: category.name || "",
|
||||||
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
|
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
seoDescription: category.seoDescription || "",
|
seoDescription: category.seoDescription || "",
|
||||||
seoTitle: category.seoTitle || ""
|
seoTitle: category.seoTitle || "",
|
||||||
|
slug: category?.slug || ""
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
backgroundImageAlt: "",
|
backgroundImageAlt: "",
|
||||||
|
@ -127,7 +129,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
name: "",
|
name: "",
|
||||||
privateMetadata: undefined,
|
privateMetadata: undefined,
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: ""
|
seoTitle: "",
|
||||||
|
slug: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (data: FormData) => {
|
const handleSubmit = (data: FormData) => {
|
||||||
|
@ -175,10 +178,13 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
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"
|
||||||
})}
|
})}
|
||||||
|
errors={errors}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={data.name}
|
titlePlaceholder={data.name}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
descriptionPlaceholder={data.name}
|
descriptionPlaceholder={data.name}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.name}
|
||||||
loading={!category}
|
loading={!category}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -511,7 +511,8 @@ export const category: (
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
seoDescription: null,
|
seoDescription: null,
|
||||||
seoTitle: null
|
seoTitle: null,
|
||||||
|
slug: "coffees"
|
||||||
});
|
});
|
||||||
export const errors = [
|
export const errors = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,6 +38,7 @@ export interface CategoryCreate_categoryCreate_category {
|
||||||
privateMetadata: (CategoryCreate_categoryCreate_category_privateMetadata | null)[];
|
privateMetadata: (CategoryCreate_categoryCreate_category_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -147,6 +147,7 @@ export interface CategoryDetails_category {
|
||||||
privateMetadata: (CategoryDetails_category_privateMetadata | null)[];
|
privateMetadata: (CategoryDetails_category_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -38,6 +38,7 @@ export interface CategoryUpdate_categoryUpdate_category {
|
||||||
privateMetadata: (CategoryUpdate_categoryUpdate_category_privateMetadata | null)[];
|
privateMetadata: (CategoryUpdate_categoryUpdate_category_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -52,7 +52,8 @@ export const CategoryCreateView: React.FC<CategoryCreateViewProps> = ({
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
title: formData.seoTitle
|
title: formData.seoTitle
|
||||||
}
|
},
|
||||||
|
slug: formData.slug
|
||||||
},
|
},
|
||||||
parent: parentId || null
|
parent: parentId || null
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,7 +186,8 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
title: formData.seoTitle
|
title: formData.seoTitle
|
||||||
}
|
},
|
||||||
|
slug: formData.slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,7 @@ export interface CollectionCreatePageFormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
description: RawDraftContentState;
|
description: RawDraftContentState;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
publicationDate: string;
|
publicationDate: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
|
@ -55,7 +56,8 @@ const initialForm: CollectionCreatePageFormData = {
|
||||||
privateMetadata: [],
|
privateMetadata: [],
|
||||||
publicationDate: "",
|
publicationDate: "",
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: ""
|
seoTitle: "",
|
||||||
|
slug: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
|
@ -133,6 +135,7 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
allowEmptySlug={true}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
descriptionPlaceholder=""
|
descriptionPlaceholder=""
|
||||||
|
@ -140,6 +143,8 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Add search engine title and description to make this collection easier to find"
|
"Add search engine title and description to make this collection easier to find"
|
||||||
})}
|
})}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.name}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={data.name}
|
titlePlaceholder={data.name}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export interface CollectionDetailsPageFormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
description: RawDraftContentState;
|
description: RawDraftContentState;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
publicationDate: string;
|
publicationDate: string;
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
seoTitle: string;
|
seoTitle: string;
|
||||||
|
@ -101,7 +102,8 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
),
|
),
|
||||||
publicationDate: maybe(() => collection.publicationDate, ""),
|
publicationDate: maybe(() => collection.publicationDate, ""),
|
||||||
seoDescription: maybe(() => collection.seoDescription, ""),
|
seoDescription: maybe(() => collection.seoDescription, ""),
|
||||||
seoTitle: maybe(() => collection.seoTitle, "")
|
seoTitle: maybe(() => collection.seoTitle, ""),
|
||||||
|
slug: collection?.slug || ""
|
||||||
}}
|
}}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
confirmLeave
|
confirmLeave
|
||||||
|
@ -149,6 +151,9 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
"Add search engine title and description to make this collection easier to find"
|
"Add search engine title and description to make this collection easier to find"
|
||||||
})}
|
})}
|
||||||
|
errors={errors}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.name}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={maybe(() => collection.name)}
|
titlePlaceholder={maybe(() => collection.name)}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
|
|
@ -167,5 +167,6 @@ export const collection: (
|
||||||
},
|
},
|
||||||
publicationDate: "2018-08-25T18:45:54.125Z",
|
publicationDate: "2018-08-25T18:45:54.125Z",
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: ""
|
seoTitle: "",
|
||||||
|
slug: "summer-collection"
|
||||||
});
|
});
|
||||||
|
|
|
@ -71,6 +71,7 @@ export interface CollectionDetails_collection {
|
||||||
metadata: (CollectionDetails_collection_metadata | null)[];
|
metadata: (CollectionDetails_collection_metadata | null)[];
|
||||||
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -34,6 +34,7 @@ export interface CollectionUpdate_collectionUpdate_collection {
|
||||||
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
||||||
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -56,6 +56,7 @@ export interface CollectionUpdateWithHomepage_collectionUpdate_collection {
|
||||||
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
||||||
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -34,6 +34,7 @@ export interface CreateCollection_collectionCreate_collection {
|
||||||
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
||||||
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -180,7 +180,8 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
seo: {
|
seo: {
|
||||||
description: formData.seoDescription,
|
description: formData.seoDescription,
|
||||||
title: formData.seoTitle
|
title: formData.seoTitle
|
||||||
}
|
},
|
||||||
|
slug: formData.slug
|
||||||
};
|
};
|
||||||
const isFeatured = data.shop.homepageCollection
|
const isFeatured = data.shop.homepageCollection
|
||||||
? data.shop.homepageCollection.id === data.collection.id
|
? data.shop.homepageCollection.id === data.collection.id
|
||||||
|
|
|
@ -4,13 +4,26 @@ 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 { getFieldError, 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 { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
||||||
|
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 SLUG_REGEX = /^[a-zA-Z0-9\-\_]+$/;
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
addressBar: {
|
addressBar: {
|
||||||
|
@ -65,9 +78,13 @@ interface SeoFormProps {
|
||||||
description?: string;
|
description?: string;
|
||||||
descriptionPlaceholder: string;
|
descriptionPlaceholder: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
errors?: Array<PageErrorFragment | ProductErrorFragment>;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
|
allowEmptySlug?: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
slugPlaceholder?: string;
|
||||||
titlePlaceholder: string;
|
titlePlaceholder: string;
|
||||||
onChange(event: any);
|
onChange(event: any);
|
||||||
onClick?();
|
onClick?();
|
||||||
|
@ -78,18 +95,61 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
description,
|
description,
|
||||||
descriptionPlaceholder,
|
descriptionPlaceholder,
|
||||||
disabled,
|
disabled,
|
||||||
|
errors = [],
|
||||||
helperText,
|
helperText,
|
||||||
|
allowEmptySlug = false,
|
||||||
loading,
|
loading,
|
||||||
title,
|
title,
|
||||||
|
slug,
|
||||||
|
slugPlaceholder,
|
||||||
titlePlaceholder,
|
titlePlaceholder,
|
||||||
onChange
|
onChange
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const [expanded, setExpansionStatus] = React.useState(false);
|
const [expanded, setExpansionStatus] = React.useState(false);
|
||||||
|
|
||||||
const toggleExpansion = () => setExpansionStatus(!expanded);
|
const toggleExpansion = () => setExpansionStatus(!expanded);
|
||||||
|
|
||||||
|
const shouldDisplayHelperText = helperText && !expanded;
|
||||||
|
|
||||||
|
const { seoFieldMessage } = defineMessages({
|
||||||
|
seoFieldMessage: {
|
||||||
|
defaultMessage: "If empty, the preview shows what will be autogenerated."
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSlugHelperMessage = () => {
|
||||||
|
const error = !!getError(SeoField.slug);
|
||||||
|
|
||||||
|
if (allowEmptySlug && !error) {
|
||||||
|
return intl.formatMessage(seoFieldMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return error ? getSlugErrorMessage() : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSlugErrorMessage = () => {
|
||||||
|
const error = getError(SeoField.slug);
|
||||||
|
const { __typename: type } = error;
|
||||||
|
|
||||||
|
return type === "ProductError"
|
||||||
|
? getProductErrorMessage(error as ProductErrorFragment, intl)
|
||||||
|
: getPageErrorMessage(error as PageErrorFragment, intl);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSlugChange = (event: React.ChangeEvent<any>) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
if (value === "" || SLUG_REGEX.test(value)) {
|
||||||
|
onChange(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getError = (fieldName: SeoField) => getFieldError(errors, fieldName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
|
@ -106,7 +166,7 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{helperText && (
|
{shouldDisplayHelperText && (
|
||||||
<Typography
|
<Typography
|
||||||
className={classNames({ [classes.helperText]: expanded })}
|
className={classNames({ [classes.helperText]: expanded })}
|
||||||
>
|
>
|
||||||
|
@ -116,13 +176,43 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<TextField
|
<TextField
|
||||||
name="seoTitle"
|
error={!!getError(SeoField.slug)}
|
||||||
|
name={SeoField.slug}
|
||||||
|
label={
|
||||||
|
<div className={classes.labelContainer}>
|
||||||
|
<div className={classes.label}>
|
||||||
|
<FormattedMessage defaultMessage="Slug" />
|
||||||
|
</div>
|
||||||
|
{slug?.length > 0 && (
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
||||||
|
description="character limit"
|
||||||
|
values={{
|
||||||
|
maxCharacters: 70,
|
||||||
|
numberOfCharacters: slug?.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
helperText={getSlugHelperMessage()}
|
||||||
|
value={slug?.slice(0, 69)}
|
||||||
|
disabled={loading || disabled}
|
||||||
|
placeholder={slug || slugify(slugPlaceholder, { lower: true })}
|
||||||
|
onChange={handleSlugChange}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<FormSpacer />
|
||||||
|
<TextField
|
||||||
|
name={SeoField.title}
|
||||||
label={
|
label={
|
||||||
<div className={classes.labelContainer}>
|
<div className={classes.labelContainer}>
|
||||||
<div className={classes.label}>
|
<div className={classes.label}>
|
||||||
<FormattedMessage defaultMessage="Search engine title" />
|
<FormattedMessage defaultMessage="Search engine title" />
|
||||||
</div>
|
</div>
|
||||||
{title.length > 0 && (
|
{title?.length > 0 && (
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
||||||
|
@ -136,11 +226,8 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage(seoFieldMessage)}
|
||||||
defaultMessage:
|
value={title?.slice(0, 69)}
|
||||||
"If empty, the preview shows what will be autogenerated."
|
|
||||||
})}
|
|
||||||
value={title.slice(0, 69)}
|
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
placeholder={titlePlaceholder}
|
placeholder={titlePlaceholder}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
@ -148,13 +235,13 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
/>
|
/>
|
||||||
<FormSpacer />
|
<FormSpacer />
|
||||||
<TextField
|
<TextField
|
||||||
name="seoDescription"
|
name={SeoField.description}
|
||||||
label={
|
label={
|
||||||
<div className={classes.labelContainer}>
|
<div className={classes.labelContainer}>
|
||||||
<div className={classes.label}>
|
<div className={classes.label}>
|
||||||
<FormattedMessage defaultMessage="Search engine description" />
|
<FormattedMessage defaultMessage="Search engine description" />
|
||||||
</div>
|
</div>
|
||||||
{description.length > 0 && (
|
{description?.length > 0 && (
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
|
||||||
|
@ -168,11 +255,8 @@ const SeoForm: React.FC<SeoFormProps> = props => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage(seoFieldMessage)}
|
||||||
defaultMessage:
|
value={description?.slice(0, 299)}
|
||||||
"If empty, the preview shows what will be autogenerated."
|
|
||||||
})}
|
|
||||||
value={description ? description.slice(0, 299) : undefined}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={loading || disabled}
|
disabled={loading || disabled}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
|
@ -24,6 +24,7 @@ export const categoryDetailsFragment = gql`
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
name
|
name
|
||||||
|
slug
|
||||||
descriptionJson
|
descriptionJson
|
||||||
seoDescription
|
seoDescription
|
||||||
seoTitle
|
seoTitle
|
||||||
|
|
|
@ -20,6 +20,7 @@ export const collectionDetailsFragment = gql`
|
||||||
alt
|
alt
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
slug
|
||||||
descriptionJson
|
descriptionJson
|
||||||
publicationDate
|
publicationDate
|
||||||
seoDescription
|
seoDescription
|
||||||
|
|
|
@ -113,6 +113,7 @@ export const productFragmentDetails = gql`
|
||||||
...ProductVariantAttributesFragment
|
...ProductVariantAttributesFragment
|
||||||
...MetadataFragment
|
...MetadataFragment
|
||||||
name
|
name
|
||||||
|
slug
|
||||||
descriptionJson
|
descriptionJson
|
||||||
seoTitle
|
seoTitle
|
||||||
seoDescription
|
seoDescription
|
||||||
|
|
|
@ -36,6 +36,7 @@ export interface CategoryDetailsFragment {
|
||||||
privateMetadata: (CategoryDetailsFragment_privateMetadata | null)[];
|
privateMetadata: (CategoryDetailsFragment_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryDetailsFragment_backgroundImage | null;
|
backgroundImage: CategoryDetailsFragment_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -32,6 +32,7 @@ export interface CollectionDetailsFragment {
|
||||||
metadata: (CollectionDetailsFragment_metadata | null)[];
|
metadata: (CollectionDetailsFragment_metadata | null)[];
|
||||||
privateMetadata: (CollectionDetailsFragment_privateMetadata | null)[];
|
privateMetadata: (CollectionDetailsFragment_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionDetailsFragment_backgroundImage | null;
|
backgroundImage: CollectionDetailsFragment_backgroundImage | null;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -213,6 +213,7 @@ export interface Product {
|
||||||
metadata: (Product_metadata | null)[];
|
metadata: (Product_metadata | null)[];
|
||||||
privateMetadata: (Product_privateMetadata | null)[];
|
privateMetadata: (Product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { useIntl } from "react-intl";
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import { PageDetails_page } from "../../types/PageDetails";
|
import { PageDetails_page } from "../../types/PageDetails";
|
||||||
import PageInfo from "../PageInfo";
|
import PageInfo from "../PageInfo";
|
||||||
import PageSlug from "../PageSlug";
|
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
content: RawDraftContentState;
|
content: RawDraftContentState;
|
||||||
|
@ -39,6 +38,7 @@ export interface PageDetailsPageProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: PageErrorFragment[];
|
errors: PageErrorFragment[];
|
||||||
page: PageDetails_page;
|
page: PageDetails_page;
|
||||||
|
allowEmptySlug?: boolean;
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
|
@ -56,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(
|
||||||
|
@ -78,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"
|
||||||
|
@ -97,6 +98,8 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
errors={errors}
|
||||||
|
allowEmptySlug={!pageExists}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
descriptionPlaceholder={maybe(
|
descriptionPlaceholder={maybe(
|
||||||
|
@ -107,6 +110,8 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.title}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
titlePlaceholder={data.title}
|
titlePlaceholder={data.title}
|
||||||
helperText={intl.formatMessage({
|
helperText={intl.formatMessage({
|
||||||
|
@ -116,12 +121,6 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<PageSlug
|
|
||||||
data={data}
|
|
||||||
disabled={disabled}
|
|
||||||
errors={errors}
|
|
||||||
onChange={change}
|
|
||||||
/>
|
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<VisibilityCard
|
<VisibilityCard
|
||||||
data={data}
|
data={data}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import Card from "@material-ui/core/Card";
|
|
||||||
import CardContent from "@material-ui/core/CardContent";
|
|
||||||
import TextField from "@material-ui/core/TextField";
|
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
|
||||||
import { PageErrorFragment } from "@saleor/fragments/types/PageErrorFragment";
|
|
||||||
import { getFormErrors } from "@saleor/utils/errors";
|
|
||||||
import getPageErrorMessage from "@saleor/utils/errors/page";
|
|
||||||
import React from "react";
|
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
import slugify from "slugify";
|
|
||||||
|
|
||||||
import { FormData } from "../PageDetailsPage";
|
|
||||||
|
|
||||||
export interface PageSlugProps {
|
|
||||||
data: FormData;
|
|
||||||
disabled: boolean;
|
|
||||||
errors: PageErrorFragment[];
|
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PageSlug: React.FC<PageSlugProps> = ({
|
|
||||||
data,
|
|
||||||
disabled,
|
|
||||||
errors,
|
|
||||||
onChange
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const formErrors = getFormErrors(["slug"], errors);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardTitle
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "URL"
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<CardContent>
|
|
||||||
<TextField
|
|
||||||
name={"slug" as keyof FormData}
|
|
||||||
disabled={disabled}
|
|
||||||
error={!!formErrors.slug}
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "Slug",
|
|
||||||
description: "page internal name"
|
|
||||||
})}
|
|
||||||
helperText={
|
|
||||||
getPageErrorMessage(formErrors.slug, intl) ||
|
|
||||||
intl.formatMessage({
|
|
||||||
defaultMessage:
|
|
||||||
"If empty, URL will be autogenerated from Page Name"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
placeholder={slugify(data.title)}
|
|
||||||
value={data.slug}
|
|
||||||
onChange={onChange}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
PageSlug.displayName = "PageSlug";
|
|
||||||
export default PageSlug;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./PageSlug";
|
|
||||||
export * from "./PageSlug";
|
|
|
@ -61,6 +61,7 @@ interface FormData extends MetadataFormData {
|
||||||
isAvailableForPurchase: boolean;
|
isAvailableForPurchase: boolean;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
productType: string;
|
productType: string;
|
||||||
publicationDate: string;
|
publicationDate: string;
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
|
@ -169,6 +170,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: "",
|
seoTitle: "",
|
||||||
sku: null,
|
sku: null,
|
||||||
|
slug: "",
|
||||||
stockQuantity: null,
|
stockQuantity: null,
|
||||||
taxCode: null,
|
taxCode: null,
|
||||||
trackInventory: false,
|
trackInventory: false,
|
||||||
|
@ -323,11 +325,14 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<SeoForm
|
<SeoForm
|
||||||
|
allowEmptySlug={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"
|
||||||
})}
|
})}
|
||||||
title={data.seoTitle}
|
title={data.seoTitle}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.name}
|
||||||
titlePlaceholder={data.name}
|
titlePlaceholder={data.name}
|
||||||
description={data.seoDescription}
|
description={data.seoDescription}
|
||||||
descriptionPlaceholder={data.seoTitle}
|
descriptionPlaceholder={data.seoTitle}
|
||||||
|
|
|
@ -374,6 +374,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}
|
||||||
|
@ -382,6 +383,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
.getPlainText()
|
.getPlainText()
|
||||||
.slice(0, 300)
|
.slice(0, 300)
|
||||||
)}
|
)}
|
||||||
|
slug={data.slug}
|
||||||
|
slugPlaceholder={data.name}
|
||||||
loading={disabled}
|
loading={disabled}
|
||||||
onClick={onSeoClick}
|
onClick={onSeoClick}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
|
|
@ -257,6 +257,7 @@ export const product: (
|
||||||
seoDescription: "Seo description",
|
seoDescription: "Seo description",
|
||||||
seoTitle: "Seo title",
|
seoTitle: "Seo title",
|
||||||
sku: "59661-34207",
|
sku: "59661-34207",
|
||||||
|
slug: "Borders",
|
||||||
taxType: {
|
taxType: {
|
||||||
__typename: "TaxType",
|
__typename: "TaxType",
|
||||||
description: "standard",
|
description: "standard",
|
||||||
|
|
|
@ -219,6 +219,7 @@ export interface ProductCreate_productCreate_product {
|
||||||
metadata: (ProductCreate_productCreate_product_metadata | null)[];
|
metadata: (ProductCreate_productCreate_product_metadata | null)[];
|
||||||
privateMetadata: (ProductCreate_productCreate_product_privateMetadata | null)[];
|
privateMetadata: (ProductCreate_productCreate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -213,6 +213,7 @@ export interface ProductDetails_product {
|
||||||
metadata: (ProductDetails_product_metadata | null)[];
|
metadata: (ProductDetails_product_metadata | null)[];
|
||||||
privateMetadata: (ProductDetails_product_privateMetadata | null)[];
|
privateMetadata: (ProductDetails_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -219,6 +219,7 @@ export interface ProductImageCreate_productImageCreate_product {
|
||||||
metadata: (ProductImageCreate_productImageCreate_product_metadata | null)[];
|
metadata: (ProductImageCreate_productImageCreate_product_metadata | null)[];
|
||||||
privateMetadata: (ProductImageCreate_productImageCreate_product_privateMetadata | null)[];
|
privateMetadata: (ProductImageCreate_productImageCreate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -219,6 +219,7 @@ export interface ProductImageUpdate_productImageUpdate_product {
|
||||||
metadata: (ProductImageUpdate_productImageUpdate_product_metadata | null)[];
|
metadata: (ProductImageUpdate_productImageUpdate_product_metadata | null)[];
|
||||||
privateMetadata: (ProductImageUpdate_productImageUpdate_product_privateMetadata | null)[];
|
privateMetadata: (ProductImageUpdate_productImageUpdate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -219,6 +219,7 @@ export interface ProductUpdate_productUpdate_product {
|
||||||
metadata: (ProductUpdate_productUpdate_product_metadata | null)[];
|
metadata: (ProductUpdate_productUpdate_product_metadata | null)[];
|
||||||
privateMetadata: (ProductUpdate_productUpdate_product_privateMetadata | null)[];
|
privateMetadata: (ProductUpdate_productUpdate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -219,6 +219,7 @@ export interface ProductVariantReorder_productVariantReorder_product {
|
||||||
metadata: (ProductVariantReorder_productVariantReorder_product_metadata | null)[];
|
metadata: (ProductVariantReorder_productVariantReorder_product_metadata | null)[];
|
||||||
privateMetadata: (ProductVariantReorder_productVariantReorder_product_privateMetadata | null)[];
|
privateMetadata: (ProductVariantReorder_productVariantReorder_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -58,12 +58,19 @@ export interface ProductVariantSetDefault_productVariantSetDefault_product_produ
|
||||||
values: (ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes_values | null)[] | null;
|
values: (ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes_values | null)[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductVariantSetDefault_productVariantSetDefault_product_productType_taxType {
|
||||||
|
__typename: "TaxType";
|
||||||
|
description: string | null;
|
||||||
|
taxCode: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductVariantSetDefault_productVariantSetDefault_product_productType {
|
export interface ProductVariantSetDefault_productVariantSetDefault_product_productType {
|
||||||
__typename: "ProductType";
|
__typename: "ProductType";
|
||||||
id: string;
|
id: string;
|
||||||
variantAttributes: (ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes | null)[] | null;
|
variantAttributes: (ProductVariantSetDefault_productVariantSetDefault_product_productType_variantAttributes | null)[] | null;
|
||||||
name: string;
|
name: string;
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
|
taxType: ProductVariantSetDefault_productVariantSetDefault_product_productType_taxType | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_start_gross {
|
export interface ProductVariantSetDefault_productVariantSetDefault_product_pricing_priceRangeUndiscounted_start_gross {
|
||||||
|
@ -197,6 +204,12 @@ export interface ProductVariantSetDefault_productVariantSetDefault_product_weigh
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductVariantSetDefault_productVariantSetDefault_product_taxType {
|
||||||
|
__typename: "TaxType";
|
||||||
|
description: string | null;
|
||||||
|
taxCode: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductVariantSetDefault_productVariantSetDefault_product {
|
export interface ProductVariantSetDefault_productVariantSetDefault_product {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -206,6 +219,7 @@ export interface ProductVariantSetDefault_productVariantSetDefault_product {
|
||||||
metadata: (ProductVariantSetDefault_productVariantSetDefault_product_metadata | null)[];
|
metadata: (ProductVariantSetDefault_productVariantSetDefault_product_metadata | null)[];
|
||||||
privateMetadata: (ProductVariantSetDefault_productVariantSetDefault_product_privateMetadata | null)[];
|
privateMetadata: (ProductVariantSetDefault_productVariantSetDefault_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
@ -222,6 +236,7 @@ export interface ProductVariantSetDefault_productVariantSetDefault_product {
|
||||||
images: (ProductVariantSetDefault_productVariantSetDefault_product_images | null)[] | null;
|
images: (ProductVariantSetDefault_productVariantSetDefault_product_images | null)[] | null;
|
||||||
variants: (ProductVariantSetDefault_productVariantSetDefault_product_variants | null)[] | null;
|
variants: (ProductVariantSetDefault_productVariantSetDefault_product_variants | null)[] | null;
|
||||||
weight: ProductVariantSetDefault_productVariantSetDefault_product_weight | null;
|
weight: ProductVariantSetDefault_productVariantSetDefault_product_weight | null;
|
||||||
|
taxType: ProductVariantSetDefault_productVariantSetDefault_product_taxType | null;
|
||||||
availableForPurchase: any | null;
|
availableForPurchase: any | null;
|
||||||
visibleInListings: boolean;
|
visibleInListings: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,6 +219,7 @@ export interface SimpleProductUpdate_productUpdate_product {
|
||||||
metadata: (SimpleProductUpdate_productUpdate_product_metadata | null)[];
|
metadata: (SimpleProductUpdate_productUpdate_product_metadata | null)[];
|
||||||
privateMetadata: (SimpleProductUpdate_productUpdate_product_privateMetadata | null)[];
|
privateMetadata: (SimpleProductUpdate_productUpdate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
|
|
|
@ -182,6 +182,7 @@ export interface ProductUpdatePageFormData extends MetadataFormData {
|
||||||
isAvailableForPurchase: boolean;
|
isAvailableForPurchase: boolean;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
slug: string;
|
||||||
publicationDate: string;
|
publicationDate: string;
|
||||||
seoDescription: string;
|
seoDescription: string;
|
||||||
seoTitle: string;
|
seoTitle: string;
|
||||||
|
@ -225,6 +226,7 @@ export function getProductUpdatePageFormData(
|
||||||
: undefined,
|
: undefined,
|
||||||
""
|
""
|
||||||
),
|
),
|
||||||
|
slug: product?.slug || "",
|
||||||
taxCode: product?.taxType.taxCode,
|
taxCode: product?.taxType.taxCode,
|
||||||
trackInventory: !!product?.variants[0]?.trackInventory,
|
trackInventory: !!product?.variants[0]?.trackInventory,
|
||||||
visibleInListings: !!product?.visibleInListings,
|
visibleInListings: !!product?.visibleInListings,
|
||||||
|
|
|
@ -113,6 +113,7 @@ export const ProductCreateView: React.FC = () => {
|
||||||
title: formData.seoTitle
|
title: formData.seoTitle
|
||||||
},
|
},
|
||||||
sku: formData.sku,
|
sku: formData.sku,
|
||||||
|
slug: formData.slug,
|
||||||
stocks: formData.stocks.map(stock => ({
|
stocks: formData.stocks.map(stock => ({
|
||||||
quantity: parseInt(stock.value, 0),
|
quantity: parseInt(stock.value, 0),
|
||||||
warehouse: stock.id
|
warehouse: stock.id
|
||||||
|
|
|
@ -60,6 +60,7 @@ export function createUpdateHandler(
|
||||||
description: data.seoDescription,
|
description: data.seoDescription,
|
||||||
title: data.seoTitle
|
title: data.seoTitle
|
||||||
},
|
},
|
||||||
|
slug: data.slug,
|
||||||
taxCode: data.changeTaxCode ? data.taxCode : null,
|
taxCode: data.changeTaxCode ? data.taxCode : null,
|
||||||
visibleInListings: data.visibleInListings
|
visibleInListings: data.visibleInListings
|
||||||
}
|
}
|
||||||
|
|
|
@ -113433,73 +113433,6 @@ Ctrl + K"
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
|
||||||
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="CardTitle-root-id"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
|
|
||||||
>
|
|
||||||
URL
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
class="CardTitle-toolbar-id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="CardTitle-children-id"
|
|
||||||
/>
|
|
||||||
<hr
|
|
||||||
class="CardTitle-hr-id"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="MuiCardContent-root-id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-filled-id"
|
|
||||||
data-shrink="true"
|
|
||||||
>
|
|
||||||
Slug
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-invalid="false"
|
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id"
|
|
||||||
name="slug"
|
|
||||||
placeholder="About"
|
|
||||||
type="text"
|
|
||||||
value="about"
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
|
|
||||||
style="padding-left:8px"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legend-id"
|
|
||||||
style="width:0"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-filled-id"
|
|
||||||
>
|
|
||||||
If empty, URL will be autogenerated from Page Name
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="CardSpacer-spacer-id"
|
class="CardSpacer-spacer-id"
|
||||||
/>
|
/>
|
||||||
|
@ -114324,73 +114257,6 @@ Ctrl + K"
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
|
||||||
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="CardTitle-root-id"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
|
|
||||||
>
|
|
||||||
URL
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
class="CardTitle-toolbar-id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="CardTitle-children-id"
|
|
||||||
/>
|
|
||||||
<hr
|
|
||||||
class="CardTitle-hr-id"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="MuiCardContent-root-id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-shrink-id MuiInputLabel-outlined-id MuiFormLabel-error-id MuiInputLabel-error-id MuiFormLabel-filled-id"
|
|
||||||
data-shrink="true"
|
|
||||||
>
|
|
||||||
Slug
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-error-id MuiOutlinedInput-error-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-invalid="true"
|
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id"
|
|
||||||
name="slug"
|
|
||||||
placeholder="About"
|
|
||||||
type="text"
|
|
||||||
value="about"
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
|
|
||||||
style="padding-left:8px"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legend-id"
|
|
||||||
style="width:0"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-error-id MuiFormHelperText-filled-id"
|
|
||||||
>
|
|
||||||
Invalid value
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="CardSpacer-spacer-id"
|
class="CardSpacer-spacer-id"
|
||||||
/>
|
/>
|
||||||
|
@ -115018,74 +114884,6 @@ Ctrl + K"
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
|
||||||
class="MuiPaper-root-id MuiPaper-elevation0-id MuiCard-root-id MuiPaper-rounded-id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="CardTitle-root-id"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root-id CardTitle-title-id MuiTypography-h5-id"
|
|
||||||
>
|
|
||||||
URL
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
class="CardTitle-toolbar-id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="CardTitle-children-id"
|
|
||||||
/>
|
|
||||||
<hr
|
|
||||||
class="CardTitle-hr-id"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="MuiCardContent-root-id"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root-id MuiTextField-root-id MuiFormControl-fullWidth-id"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root-id MuiInputLabel-root-id MuiInputLabel-formControl-id MuiInputLabel-animated-id MuiInputLabel-outlined-id MuiFormLabel-disabled-id MuiInputLabel-disabled-id"
|
|
||||||
data-shrink="false"
|
|
||||||
>
|
|
||||||
Slug
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root-id MuiOutlinedInput-root-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiInputBase-fullWidth-id MuiInputBase-formControl-id"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-invalid="false"
|
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id"
|
|
||||||
disabled=""
|
|
||||||
name="slug"
|
|
||||||
placeholder=""
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-id MuiOutlinedInput-notchedOutline-id"
|
|
||||||
style="padding-left:8px"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legend-id"
|
|
||||||
style="width:0.01px"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="MuiFormHelperText-root-id MuiFormHelperText-contained-id MuiFormHelperText-disabled-id"
|
|
||||||
>
|
|
||||||
If empty, URL will be autogenerated from Page Name
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="CardSpacer-spacer-id"
|
class="CardSpacer-spacer-id"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import { PageErrorFragment } from "@saleor/fragments/types/PageErrorFragment";
|
import { PageErrorFragment } from "@saleor/fragments/types/PageErrorFragment";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { PageErrorCode } from "@saleor/types/globalTypes";
|
import { PageErrorCode } from "@saleor/types/globalTypes";
|
||||||
import { IntlShape } from "react-intl";
|
import { defineMessages, IntlShape } from "react-intl";
|
||||||
|
|
||||||
import commonErrorMessages from "./common";
|
import commonErrorMessages from "./common";
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
nameAlreadyTaken: {
|
||||||
|
defaultMessage: "This name is already taken. Please provide another."
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function getPageErrorMessage(
|
function getPageErrorMessage(
|
||||||
err: Omit<PageErrorFragment, "__typename"> | undefined,
|
err: Omit<PageErrorFragment, "__typename"> | undefined,
|
||||||
intl: IntlShape
|
intl: IntlShape
|
||||||
|
@ -17,6 +23,8 @@ function getPageErrorMessage(
|
||||||
return intl.formatMessage(commonMessages.requiredField);
|
return intl.formatMessage(commonMessages.requiredField);
|
||||||
case PageErrorCode.INVALID:
|
case PageErrorCode.INVALID:
|
||||||
return intl.formatMessage(commonErrorMessages.invalid);
|
return intl.formatMessage(commonErrorMessages.invalid);
|
||||||
|
case PageErrorCode.UNIQUE:
|
||||||
|
return intl.formatMessage(messages.nameAlreadyTaken);
|
||||||
default:
|
default:
|
||||||
return intl.formatMessage(commonErrorMessages.unknownError);
|
return intl.formatMessage(commonErrorMessages.unknownError);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,9 @@ const messages = defineMessages({
|
||||||
duplicatedInputItem: {
|
duplicatedInputItem: {
|
||||||
defaultMessage: "Variant with these attributes already exists"
|
defaultMessage: "Variant with these attributes already exists"
|
||||||
},
|
},
|
||||||
|
nameAlreadyTaken: {
|
||||||
|
defaultMessage: "This name is already taken. Please provide another."
|
||||||
|
},
|
||||||
skuUnique: {
|
skuUnique: {
|
||||||
defaultMessage: "SKUs must be unique",
|
defaultMessage: "SKUs must be unique",
|
||||||
description: "bulk variant create error"
|
description: "bulk variant create error"
|
||||||
|
@ -64,6 +67,8 @@ function getProductErrorMessage(
|
||||||
return intl.formatMessage(messages.variantNoDigitalContent);
|
return intl.formatMessage(messages.variantNoDigitalContent);
|
||||||
case ProductErrorCode.INVALID:
|
case ProductErrorCode.INVALID:
|
||||||
return intl.formatMessage(commonErrorMessages.invalid);
|
return intl.formatMessage(commonErrorMessages.invalid);
|
||||||
|
case ProductErrorCode.UNIQUE:
|
||||||
|
return intl.formatMessage(messages.nameAlreadyTaken);
|
||||||
default:
|
default:
|
||||||
return intl.formatMessage(commonErrorMessages.unknownError);
|
return intl.formatMessage(commonErrorMessages.unknownError);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue