Add rich text editor to category pages
This commit is contained in:
parent
28cc74c954
commit
9adde24a48
5 changed files with 391 additions and 268 deletions
|
@ -2,43 +2,23 @@ import AppHeader from "@saleor/components/AppHeader";
|
|||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import Metadata from "@saleor/components/Metadata";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import CategoryDetailsForm from "../../components/CategoryDetailsForm";
|
||||
|
||||
export interface FormData extends MetadataFormData {
|
||||
description: RawDraftContentState;
|
||||
name: string;
|
||||
slug: string;
|
||||
seoTitle: string;
|
||||
seoDescription: string;
|
||||
}
|
||||
|
||||
const initialData: FormData = {
|
||||
description: convertToRaw(ContentState.createFromText("")),
|
||||
metadata: [],
|
||||
name: "",
|
||||
privateMetadata: [],
|
||||
seoDescription: "",
|
||||
seoTitle: "",
|
||||
slug: ""
|
||||
};
|
||||
import CategoryCreateForm, { CategoryCreateData } from "./form";
|
||||
|
||||
export interface CategoryCreatePageProps {
|
||||
errors: ProductErrorFragment[];
|
||||
disabled: boolean;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onSubmit(data: FormData);
|
||||
onSubmit(data: CategoryCreateData);
|
||||
onBack();
|
||||
}
|
||||
|
||||
|
@ -50,63 +30,57 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
|
|||
saveButtonBarState
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
return (
|
||||
<Form onSubmit={onSubmit} initial={initialData} confirmLeave>
|
||||
{({ data, change, submit, hasChanged }) => {
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create New Category",
|
||||
description: "page header"
|
||||
})}
|
||||
<CategoryCreateForm onSubmit={onSubmit}>
|
||||
{({ data, change, handlers, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create New Category",
|
||||
description: "page header"
|
||||
})}
|
||||
/>
|
||||
<div>
|
||||
<CategoryDetailsForm
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
onDescriptionChange={handlers.changeDescription}
|
||||
/>
|
||||
<div>
|
||||
<CategoryDetailsForm
|
||||
disabled={disabled}
|
||||
data={data}
|
||||
onChange={change}
|
||||
errors={errors}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
allowEmptySlug={true}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
})}
|
||||
slug={data.slug}
|
||||
slugPlaceholder={data.name}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={data.name}
|
||||
loading={disabled}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
allowEmptySlug={true}
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
})}
|
||||
slug={data.slug}
|
||||
slugPlaceholder={data.name}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={data.name}
|
||||
loading={disabled}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={handlers.changeMetadata} />
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
)}
|
||||
</CategoryCreateForm>
|
||||
);
|
||||
};
|
||||
CategoryCreatePage.displayName = "CategoryCreatePage";
|
||||
|
|
100
src/categories/components/CategoryCreatePage/form.tsx
Normal file
100
src/categories/components/CategoryCreatePage/form.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import useRichText from "@saleor/utils/richText/useRichText";
|
||||
import React from "react";
|
||||
|
||||
export interface CategoryCreateFormData extends MetadataFormData {
|
||||
name: string;
|
||||
seoDescription: string;
|
||||
seoTitle: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface CategoryCreateData extends CategoryCreateFormData {
|
||||
description: OutputData;
|
||||
}
|
||||
|
||||
interface CategoryCreateHandlers {
|
||||
changeMetadata: FormChange;
|
||||
changeDescription: RichTextEditorChange;
|
||||
}
|
||||
export interface UseCategoryCreateFormResult {
|
||||
change: FormChange;
|
||||
data: CategoryCreateData;
|
||||
handlers: CategoryCreateHandlers;
|
||||
hasChanged: boolean;
|
||||
submit: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface CategoryCreateFormProps {
|
||||
children: (props: UseCategoryCreateFormResult) => React.ReactNode;
|
||||
onSubmit: (data: CategoryCreateData) => Promise<any[]>;
|
||||
}
|
||||
|
||||
function useCategoryCreateForm(
|
||||
onSubmit: (data: CategoryCreateData) => Promise<any[]>
|
||||
): UseCategoryCreateFormResult {
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
const triggerChange = () => setChanged(true);
|
||||
|
||||
const form = useForm<CategoryCreateFormData>({
|
||||
metadata: [],
|
||||
name: "",
|
||||
privateMetadata: [],
|
||||
seoDescription: "",
|
||||
seoTitle: "",
|
||||
slug: ""
|
||||
});
|
||||
const [description, changeDescription] = useRichText({
|
||||
initial: null,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const handleChange: FormChange = (event, cb) => {
|
||||
form.change(event, cb);
|
||||
triggerChange();
|
||||
};
|
||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||
|
||||
// Need to make it function to always have description.current up to date
|
||||
const getData = (): CategoryCreateData => ({
|
||||
...form.data,
|
||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
|
||||
description: description.current
|
||||
});
|
||||
|
||||
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data: getData(),
|
||||
handlers: {
|
||||
changeDescription,
|
||||
changeMetadata
|
||||
},
|
||||
hasChanged: changed,
|
||||
submit
|
||||
};
|
||||
}
|
||||
|
||||
const CategoryCreateForm: React.FC<CategoryCreateFormProps> = ({
|
||||
children,
|
||||
onSubmit
|
||||
}) => {
|
||||
const props = useCategoryCreateForm(onSubmit);
|
||||
|
||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||
};
|
||||
|
||||
CategoryCreateForm.displayName = "CategoryCreateForm";
|
||||
export default CategoryCreateForm;
|
|
@ -1,35 +1,34 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
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 FormSpacer from "@saleor/components/FormSpacer";
|
||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||
import RichTextEditor, {
|
||||
RichTextEditorChange
|
||||
} from "@saleor/components/RichTextEditor";
|
||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { maybe } from "../../../misc";
|
||||
import { CategoryDetails_category } from "../../types/CategoryDetails";
|
||||
|
||||
interface CategoryDetailsFormProps {
|
||||
category?: CategoryDetails_category;
|
||||
data: {
|
||||
name: string;
|
||||
description: RawDraftContentState;
|
||||
description: OutputData;
|
||||
};
|
||||
disabled: boolean;
|
||||
errors: ProductErrorFragment[];
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
onDescriptionChange: RichTextEditorChange;
|
||||
}
|
||||
|
||||
export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
|
||||
category,
|
||||
disabled,
|
||||
data,
|
||||
onChange,
|
||||
onDescriptionChange,
|
||||
errors
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
@ -58,15 +57,15 @@ export const CategoryDetailsForm: React.FC<CategoryDetailsFormProps> = ({
|
|||
</div>
|
||||
<FormSpacer />
|
||||
<RichTextEditor
|
||||
data={data.description}
|
||||
disabled={disabled}
|
||||
error={!!formErrors.descriptionJson}
|
||||
helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Category Description"
|
||||
})}
|
||||
initial={maybe(() => JSON.parse(category.descriptionJson))}
|
||||
name="description"
|
||||
onChange={onChange}
|
||||
onChange={onDescriptionChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -5,9 +5,7 @@ import { CardSpacer } from "@saleor/components/CardSpacer";
|
|||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
|
@ -15,9 +13,6 @@ import { Tab, TabContainer } from "@saleor/components/Tab";
|
|||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -32,15 +27,7 @@ import {
|
|||
} from "../../types/CategoryDetails";
|
||||
import CategoryBackground from "../CategoryBackground";
|
||||
import CategoryProducts from "../CategoryProducts";
|
||||
|
||||
export interface FormData extends MetadataFormData {
|
||||
backgroundImageAlt: string;
|
||||
description: RawDraftContentState;
|
||||
name: string;
|
||||
slug: string;
|
||||
seoTitle: string;
|
||||
seoDescription: string;
|
||||
}
|
||||
import CategoryUpdateForm, { CategoryUpdateData } from "./form";
|
||||
|
||||
export enum CategoryPageTab {
|
||||
categories = "categories",
|
||||
|
@ -62,7 +49,7 @@ export interface CategoryUpdatePageProps
|
|||
};
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onImageDelete: () => void;
|
||||
onSubmit: (data: FormData) => SubmitPromise;
|
||||
onSubmit: (data: CategoryUpdateData) => SubmitPromise;
|
||||
onImageUpload(file: File);
|
||||
onNextPage();
|
||||
onPreviousPage();
|
||||
|
@ -106,180 +93,136 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
|||
toggleAll
|
||||
}: CategoryUpdatePageProps) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const initialData: FormData = category
|
||||
? {
|
||||
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
|
||||
description: maybe(() => JSON.parse(category.descriptionJson)),
|
||||
metadata: category?.metadata?.map(mapMetadataItemToInput),
|
||||
name: category.name || "",
|
||||
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
seoDescription: category.seoDescription || "",
|
||||
seoTitle: category.seoTitle || "",
|
||||
slug: category?.slug || ""
|
||||
}
|
||||
: {
|
||||
backgroundImageAlt: "",
|
||||
description: "",
|
||||
metadata: undefined,
|
||||
name: "",
|
||||
privateMetadata: undefined,
|
||||
seoDescription: "",
|
||||
seoTitle: "",
|
||||
slug: ""
|
||||
};
|
||||
|
||||
const handleSubmit = (data: FormData) => {
|
||||
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||
const privateMetadata = isPrivateMetadataModified
|
||||
? data.privateMetadata
|
||||
: undefined;
|
||||
|
||||
return onSubmit({
|
||||
...data,
|
||||
metadata,
|
||||
privateMetadata
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
|
||||
{({ data, change, submit, hasChanged }) => {
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
</AppHeader>
|
||||
<PageHeader title={category ? category.name : undefined} />
|
||||
<CategoryDetailsForm
|
||||
category={category}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<CategoryBackground
|
||||
data={data}
|
||||
onImageUpload={onImageUpload}
|
||||
onImageDelete={onImageDelete}
|
||||
image={maybe(() => category.backgroundImage)}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
})}
|
||||
errors={errors}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={data.name}
|
||||
slug={data.slug}
|
||||
slugPlaceholder={data.name}
|
||||
loading={!category}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
<CardSpacer />
|
||||
<TabContainer>
|
||||
<CategoriesTab
|
||||
isActive={currentTab === CategoryPageTab.categories}
|
||||
changeTab={changeTab}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Subcategories"
|
||||
description="number of subcategories in category"
|
||||
/>
|
||||
</CategoriesTab>
|
||||
<ProductsTab
|
||||
isActive={currentTab === CategoryPageTab.products}
|
||||
changeTab={changeTab}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Products"
|
||||
description="number of products in category"
|
||||
/>
|
||||
</ProductsTab>
|
||||
</TabContainer>
|
||||
<CardSpacer />
|
||||
{currentTab === CategoryPageTab.categories && (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "All Subcategories",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button
|
||||
color="primary"
|
||||
variant="text"
|
||||
onClick={onAddCategory}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create subcategory"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<CategoryList
|
||||
categories={subcategories}
|
||||
disabled={disabled}
|
||||
isChecked={isChecked}
|
||||
isRoot={false}
|
||||
pageInfo={pageInfo}
|
||||
selected={selected}
|
||||
sort={undefined}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={subcategoryListToolbar}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onCategoryClick}
|
||||
onSort={() => undefined}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
{currentTab === CategoryPageTab.products && (
|
||||
<CategoryProducts
|
||||
categoryName={maybe(() => category.name)}
|
||||
products={products}
|
||||
<CategoryUpdateForm category={category} onSubmit={onSubmit}>
|
||||
{({ data, change, handlers, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
</AppHeader>
|
||||
<PageHeader title={category?.name} />
|
||||
<CategoryDetailsForm
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
onDescriptionChange={handlers.changeDescription}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<CategoryBackground
|
||||
data={data}
|
||||
onImageUpload={onImageUpload}
|
||||
onImageDelete={onImageDelete}
|
||||
image={maybe(() => category.backgroundImage)}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<SeoForm
|
||||
helperText={intl.formatMessage({
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this category easier to find"
|
||||
})}
|
||||
errors={errors}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.name}
|
||||
description={data.seoDescription}
|
||||
descriptionPlaceholder={data.name}
|
||||
slug={data.slug}
|
||||
slugPlaceholder={data.name}
|
||||
loading={!category}
|
||||
onChange={change}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={handlers.changeMetadata} />
|
||||
<CardSpacer />
|
||||
<TabContainer>
|
||||
<CategoriesTab
|
||||
isActive={currentTab === CategoryPageTab.categories}
|
||||
changeTab={changeTab}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Subcategories"
|
||||
description="number of subcategories in category"
|
||||
/>
|
||||
</CategoriesTab>
|
||||
<ProductsTab
|
||||
isActive={currentTab === CategoryPageTab.products}
|
||||
changeTab={changeTab}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Products"
|
||||
description="number of products in category"
|
||||
/>
|
||||
</ProductsTab>
|
||||
</TabContainer>
|
||||
<CardSpacer />
|
||||
{currentTab === CategoryPageTab.categories && (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "All Subcategories",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button
|
||||
color="primary"
|
||||
variant="text"
|
||||
onClick={onAddCategory}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Create subcategory"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<CategoryList
|
||||
categories={subcategories}
|
||||
disabled={disabled}
|
||||
isChecked={isChecked}
|
||||
isRoot={false}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onProductClick}
|
||||
onAdd={onAddProduct}
|
||||
selected={selected}
|
||||
sort={undefined}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
selected={selected}
|
||||
isChecked={isChecked}
|
||||
toolbar={productListToolbar}
|
||||
toolbar={subcategoryListToolbar}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onCategoryClick}
|
||||
onSort={() => undefined}
|
||||
/>
|
||||
)}
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onDelete={onDelete}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
</Card>
|
||||
)}
|
||||
{currentTab === CategoryPageTab.products && (
|
||||
<CategoryProducts
|
||||
categoryName={category?.name}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onProductClick}
|
||||
onAdd={onAddProduct}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
selected={selected}
|
||||
isChecked={isChecked}
|
||||
toolbar={productListToolbar}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
)}
|
||||
<SaveButtonBar
|
||||
onCancel={onBack}
|
||||
onDelete={onDelete}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</CategoryUpdateForm>
|
||||
);
|
||||
};
|
||||
CategoryUpdatePage.displayName = "CategoryUpdatePage";
|
||||
|
|
107
src/categories/components/CategoryUpdatePage/form.tsx
Normal file
107
src/categories/components/CategoryUpdatePage/form.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { OutputData } from "@editorjs/editorjs";
|
||||
import { CategoryDetails_category } from "@saleor/categories/types/CategoryDetails";
|
||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import useRichText from "@saleor/utils/richText/useRichText";
|
||||
import React from "react";
|
||||
|
||||
export interface CategoryUpdateFormData extends MetadataFormData {
|
||||
backgroundImageAlt: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
seoTitle: string;
|
||||
seoDescription: string;
|
||||
}
|
||||
export interface CategoryUpdateData extends CategoryUpdateFormData {
|
||||
description: OutputData;
|
||||
}
|
||||
|
||||
interface CategoryUpdateHandlers {
|
||||
changeMetadata: FormChange;
|
||||
changeDescription: RichTextEditorChange;
|
||||
}
|
||||
export interface UseCategoryUpdateFormResult {
|
||||
change: FormChange;
|
||||
data: CategoryUpdateData;
|
||||
handlers: CategoryUpdateHandlers;
|
||||
hasChanged: boolean;
|
||||
submit: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface CategoryUpdateFormProps {
|
||||
children: (props: UseCategoryUpdateFormResult) => React.ReactNode;
|
||||
category: CategoryDetails_category;
|
||||
onSubmit: (data: CategoryUpdateData) => Promise<any[]>;
|
||||
}
|
||||
|
||||
function useCategoryUpdateForm(
|
||||
category: CategoryDetails_category,
|
||||
onSubmit: (data: CategoryUpdateData) => Promise<any[]>
|
||||
): UseCategoryUpdateFormResult {
|
||||
const [changed, setChanged] = React.useState(false);
|
||||
const triggerChange = () => setChanged(true);
|
||||
|
||||
const form = useForm<CategoryUpdateFormData>({
|
||||
backgroundImageAlt: category?.backgroundImage?.alt || "",
|
||||
metadata: category?.metadata?.map(mapMetadataItemToInput),
|
||||
name: category?.name || "",
|
||||
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
seoDescription: category?.seoDescription || "",
|
||||
seoTitle: category?.seoTitle || "",
|
||||
slug: category?.slug || ""
|
||||
});
|
||||
const [description, changeDescription] = useRichText({
|
||||
initial: category?.descriptionJson,
|
||||
triggerChange
|
||||
});
|
||||
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const handleChange: FormChange = (event, cb) => {
|
||||
form.change(event, cb);
|
||||
triggerChange();
|
||||
};
|
||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||
|
||||
// Need to make it function to always have description.current up to date
|
||||
const getData = (): CategoryUpdateData => ({
|
||||
...form.data,
|
||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
|
||||
description: description.current
|
||||
});
|
||||
|
||||
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data: getData(),
|
||||
handlers: {
|
||||
changeDescription,
|
||||
changeMetadata
|
||||
},
|
||||
hasChanged: changed,
|
||||
submit
|
||||
};
|
||||
}
|
||||
|
||||
const CategoryUpdateForm: React.FC<CategoryUpdateFormProps> = ({
|
||||
children,
|
||||
category,
|
||||
onSubmit
|
||||
}) => {
|
||||
const props = useCategoryUpdateForm(category, onSubmit);
|
||||
|
||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||
};
|
||||
|
||||
CategoryUpdateForm.displayName = "CategoryUpdateForm";
|
||||
export default CategoryUpdateForm;
|
Loading…
Reference in a new issue