Add rich text editor to collection pages

This commit is contained in:
dominik-zeglen 2020-11-03 17:49:42 +01:00
parent 06ef285d15
commit b27f28c822
5 changed files with 454 additions and 316 deletions

View file

@ -2,9 +2,8 @@ import AppHeader from "@saleor/components/AppHeader";
import { CardSpacer } from "@saleor/components/CardSpacer"; import { CardSpacer } from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { Container } from "@saleor/components/Container"; import { Container } from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata"; import Metadata from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
@ -12,54 +11,21 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import CollectionDetails from "../CollectionDetails/CollectionDetails"; import CollectionDetails from "../CollectionDetails/CollectionDetails";
import { CollectionImage } from "../CollectionImage/CollectionImage"; import { CollectionImage } from "../CollectionImage/CollectionImage";
import CollectionCreateForm, { CollectionCreateData } from "./form";
export interface CollectionCreatePageFormData extends MetadataFormData {
backgroundImage: {
url: string;
value: string;
};
backgroundImageAlt: string;
description: RawDraftContentState;
name: string;
slug: string;
publicationDate: string;
isPublished: boolean;
seoDescription: string;
seoTitle: string;
}
export interface CollectionCreatePageProps { export interface CollectionCreatePageProps {
disabled: boolean; disabled: boolean;
errors: ProductErrorFragment[]; errors: ProductErrorFragment[];
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
onBack: () => void; onBack: () => void;
onSubmit: (data: CollectionCreatePageFormData) => void; onSubmit: (data: CollectionCreateData) => Promise<any[]>;
} }
const initialForm: CollectionCreatePageFormData = {
backgroundImage: {
url: null,
value: null
},
backgroundImageAlt: "",
description: convertToRaw(ContentState.createFromText("")),
isPublished: false,
metadata: [],
name: "",
privateMetadata: [],
publicationDate: "",
seoDescription: "",
seoTitle: "",
slug: ""
};
const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
disabled, disabled,
errors, errors,
@ -69,127 +35,121 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
}: CollectionCreatePageProps) => { }: CollectionCreatePageProps) => {
const intl = useIntl(); const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
const {
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
return ( return (
<Form initial={initialForm} onSubmit={onSubmit}> <CollectionCreateForm onSubmit={onSubmit}>
{({ change, data, hasChanged, submit }) => { {({ change, data, handlers, hasChanged, submit }) => (
const changeMetadata = makeMetadataChangeHandler(change); <Container>
<AppHeader onBack={onBack}>
return ( {intl.formatMessage(sectionNames.collections)}
<Container> </AppHeader>
<AppHeader onBack={onBack}> <PageHeader
{intl.formatMessage(sectionNames.collections)} title={intl.formatMessage({
</AppHeader> defaultMessage: "Add Collection",
<PageHeader description: "page header"
title={intl.formatMessage({ })}
defaultMessage: "Add Collection", />
description: "page header" <Grid>
})} <div>
/> <CollectionDetails
<Grid> data={data}
<div> disabled={disabled}
<CollectionDetails errors={errors}
data={data} onChange={change}
disabled={disabled} onDescriptionChange={handlers.changeDescription}
errors={errors} />
onChange={change} <CardSpacer />
/> <CollectionImage
<CardSpacer /> image={
<CollectionImage data.backgroundImage.url
image={ ? {
data.backgroundImage.url __typename: "Image",
? { alt: data.backgroundImageAlt,
__typename: "Image", url: data.backgroundImage.url
alt: data.backgroundImageAlt,
url: data.backgroundImage.url
}
: null
}
onImageDelete={() =>
change({
target: {
name: "backgroundImage",
value: {
url: null,
value: null
}
} }
} as any) : null
} }
onImageUpload={file => onImageDelete={() =>
change({ change({
target: { target: {
name: "backgroundImage", name: "backgroundImage",
value: { value: {
url: URL.createObjectURL(file), url: null,
value: file value: null
}
} }
} as any) }
} } as any)
onChange={change} }
data={data} onImageUpload={file =>
/> change({
<CardSpacer /> target: {
<SeoForm name: "backgroundImage",
allowEmptySlug={true} value: {
description={data.seoDescription} url: URL.createObjectURL(file),
disabled={disabled} value: file
descriptionPlaceholder=""
helperText={intl.formatMessage({
defaultMessage:
"Add search engine title and description to make this collection easier to find"
})}
slug={data.slug}
slugPlaceholder={data.name}
title={data.seoTitle}
titlePlaceholder={data.name}
onChange={change}
/>
<CardSpacer />
<Metadata data={data} onChange={changeMetadata} />
</div>
<div>
<VisibilityCard
data={data}
errors={errors}
disabled={disabled}
messages={{
hiddenLabel: intl.formatMessage({
defaultMessage: "Hidden",
description: "collection label"
}),
hiddenSecondLabel: intl.formatMessage(
{
defaultMessage: "will be visible from {date}",
description: "collection"
},
{
date: localizeDate(data.publicationDate, "L")
} }
), }
visibleLabel: intl.formatMessage({ } as any)
defaultMessage: "Visible", }
description: "collection label" onChange={change}
}) data={data}
}} />
onChange={change} <CardSpacer />
/> <SeoForm
</div> allowEmptySlug={true}
</Grid> description={data.seoDescription}
<SaveButtonBar disabled={disabled}
state={saveButtonBarState} descriptionPlaceholder=""
disabled={disabled || !hasChanged} helperText={intl.formatMessage({
onCancel={onBack} defaultMessage:
onSave={submit} "Add search engine title and description to make this collection easier to find"
/> })}
</Container> slug={data.slug}
); slugPlaceholder={data.name}
}} title={data.seoTitle}
</Form> titlePlaceholder={data.name}
onChange={change}
/>
<CardSpacer />
<Metadata data={data} onChange={handlers.changeMetadata} />
</div>
<div>
<VisibilityCard
data={data}
errors={errors}
disabled={disabled}
messages={{
hiddenLabel: intl.formatMessage({
defaultMessage: "Hidden",
description: "collection label"
}),
hiddenSecondLabel: intl.formatMessage(
{
defaultMessage: "will be visible from {date}",
description: "collection"
},
{
date: localizeDate(data.publicationDate, "L")
}
),
visibleLabel: intl.formatMessage({
defaultMessage: "Visible",
description: "collection label"
})
}}
onChange={change}
/>
</div>
</Grid>
<SaveButtonBar
state={saveButtonBarState}
disabled={disabled || !hasChanged}
onCancel={onBack}
onSave={submit}
/>
</Container>
)}
</CollectionCreateForm>
); );
}; };
CollectionCreatePage.displayName = "CollectionCreatePage"; CollectionCreatePage.displayName = "CollectionCreatePage";

View file

@ -0,0 +1,111 @@
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 CollectionCreateFormData extends MetadataFormData {
backgroundImage: {
url: string;
value: string;
};
backgroundImageAlt: string;
name: string;
slug: string;
publicationDate: string;
isPublished: boolean;
seoDescription: string;
seoTitle: string;
}
export interface CollectionCreateData extends CollectionCreateFormData {
description: OutputData;
}
interface CollectionCreateHandlers {
changeMetadata: FormChange;
changeDescription: RichTextEditorChange;
}
export interface UseCollectionCreateFormResult {
change: FormChange;
data: CollectionCreateData;
handlers: CollectionCreateHandlers;
hasChanged: boolean;
submit: () => Promise<boolean>;
}
export interface CollectionCreateFormProps {
children: (props: UseCollectionCreateFormResult) => React.ReactNode;
onSubmit: (data: CollectionCreateData) => Promise<any[]>;
}
function useCollectionCreateForm(
onSubmit: (data: CollectionCreateData) => Promise<any[]>
): UseCollectionCreateFormResult {
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
const form = useForm<CollectionCreateFormData>({
backgroundImage: {
url: null,
value: null
},
backgroundImageAlt: "",
isPublished: false,
metadata: [],
name: "",
privateMetadata: [],
publicationDate: "",
seoDescription: "",
seoTitle: "",
slug: ""
});
const [description, changeDescription] = useRichText({
initial: null,
triggerChange
});
const {
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 = (): CollectionCreateData => ({
...form.data,
description: description.current
});
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
return {
change: handleChange,
data: getData(),
handlers: {
changeDescription,
changeMetadata
},
hasChanged: changed,
submit
};
}
const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
children,
onSubmit
}) => {
const props = useCollectionCreateForm(onSubmit);
return <form onSubmit={props.submit}>{children(props)}</form>;
};
CollectionCreateForm.displayName = "CollectionCreateForm";
export default CollectionCreateForm;

View file

@ -1,35 +1,34 @@
import { OutputData } from "@editorjs/editorjs";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import FormSpacer from "@saleor/components/FormSpacer"; 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 { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc";
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
import { RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { CollectionDetails_collection } from "../../types/CollectionDetails";
export interface CollectionDetailsProps { export interface CollectionDetailsProps {
collection?: CollectionDetails_collection;
data: { data: {
description: RawDraftContentState; description: OutputData;
name: string; name: string;
}; };
disabled: boolean; disabled: boolean;
errors: ProductErrorFragment[]; errors: ProductErrorFragment[];
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
onDescriptionChange: RichTextEditorChange;
} }
const CollectionDetails: React.FC<CollectionDetailsProps> = ({ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
collection,
disabled, disabled,
data, data,
onChange, onChange,
onDescriptionChange,
errors errors
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -57,13 +56,13 @@ const CollectionDetails: React.FC<CollectionDetailsProps> = ({
/> />
<FormSpacer /> <FormSpacer />
<RichTextEditor <RichTextEditor
data={data.description}
error={!!formErrors.descriptionJson} error={!!formErrors.descriptionJson}
helperText={getProductErrorMessage(formErrors.descriptionJson, intl)} helperText={getProductErrorMessage(formErrors.descriptionJson, intl)}
initial={maybe(() => JSON.parse(collection.descriptionJson))}
label={intl.formatMessage(commonMessages.description)} label={intl.formatMessage(commonMessages.description)}
name="description" name="description"
disabled={disabled} disabled={disabled}
onChange={onChange} onChange={onDescriptionChange}
/> />
</CardContent> </CardContent>
</Card> </Card>

View file

@ -3,12 +3,10 @@ import { CardSpacer } from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import { Container } from "@saleor/components/Container"; import { Container } from "@saleor/components/Container";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import Form from "@saleor/components/Form";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import Metadata from "@saleor/components/Metadata/Metadata"; import Metadata from "@saleor/components/Metadata/Metadata";
import { MetadataFormData } from "@saleor/components/Metadata/types";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
@ -16,9 +14,6 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import { sectionNames } from "@saleor/intl"; 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 React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -28,18 +23,7 @@ import { CollectionDetails_collection } from "../../types/CollectionDetails";
import CollectionDetails from "../CollectionDetails/CollectionDetails"; import CollectionDetails from "../CollectionDetails/CollectionDetails";
import { CollectionImage } from "../CollectionImage/CollectionImage"; import { CollectionImage } from "../CollectionImage/CollectionImage";
import CollectionProducts from "../CollectionProducts/CollectionProducts"; import CollectionProducts from "../CollectionProducts/CollectionProducts";
import CollectionUpdateForm, { CollectionUpdateData } from "./form";
export interface CollectionDetailsPageFormData extends MetadataFormData {
backgroundImageAlt: string;
description: RawDraftContentState;
name: string;
slug: string;
publicationDate: string;
seoDescription: string;
seoTitle: string;
isFeatured: boolean;
isPublished: boolean;
}
export interface CollectionDetailsPageProps extends PageListProps, ListActions { export interface CollectionDetailsPageProps extends PageListProps, ListActions {
collection: CollectionDetails_collection; collection: CollectionDetails_collection;
@ -51,7 +35,7 @@ export interface CollectionDetailsPageProps extends PageListProps, ListActions {
onImageDelete: () => void; onImageDelete: () => void;
onImageUpload: (file: File) => void; onImageUpload: (file: File) => void;
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void; onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
onSubmit: (data: CollectionDetailsPageFormData) => void; onSubmit: (data: CollectionUpdateData) => Promise<any>;
} }
const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
@ -69,150 +53,113 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
}: CollectionDetailsPageProps) => { }: CollectionDetailsPageProps) => {
const intl = useIntl(); const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
const {
isMetadataModified,
isPrivateMetadataModified,
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const handleSubmit = (data: CollectionDetailsPageFormData) => {
const metadata = isMetadataModified ? data.metadata : undefined;
const privateMetadata = isPrivateMetadataModified
? data.privateMetadata
: undefined;
return onSubmit({
...data,
isPublished: data.isPublished || !!data.publicationDate,
metadata,
privateMetadata
});
};
return ( return (
<Form <CollectionUpdateForm
initial={{ collection={collection}
backgroundImageAlt: maybe(() => collection.backgroundImage.alt, ""), isFeatured={isFeatured}
description: maybe(() => JSON.parse(collection.descriptionJson)), onSubmit={onSubmit}
isFeatured,
isPublished: maybe(() => collection.isPublished, false),
metadata: collection?.metadata?.map(mapMetadataItemToInput),
name: maybe(() => collection.name, ""),
privateMetadata: collection?.privateMetadata?.map(
mapMetadataItemToInput
),
publicationDate: maybe(() => collection.publicationDate, ""),
seoDescription: maybe(() => collection.seoDescription, ""),
seoTitle: maybe(() => collection.seoTitle, ""),
slug: collection?.slug || ""
}}
onSubmit={handleSubmit}
confirmLeave
> >
{({ change, data, hasChanged, submit }) => { {({ change, data, handlers, hasChanged, submit }) => (
const changeMetadata = makeMetadataChangeHandler(change); <Container>
<AppHeader onBack={onBack}>
return ( {intl.formatMessage(sectionNames.collections)}
<Container> </AppHeader>
<AppHeader onBack={onBack}> <PageHeader title={maybe(() => collection.name)} />
{intl.formatMessage(sectionNames.collections)} <Grid>
</AppHeader> <div>
<PageHeader title={maybe(() => collection.name)} /> <CollectionDetails
<Grid> data={data}
disabled={disabled}
errors={errors}
onChange={change}
onDescriptionChange={handlers.changeDescription}
/>
<CardSpacer />
<CollectionImage
data={data}
image={maybe(() => collection.backgroundImage)}
onImageDelete={onImageDelete}
onImageUpload={onImageUpload}
onChange={change}
/>
<CardSpacer />
<Metadata data={data} onChange={handlers.changeMetadata} />
<CardSpacer />
<CollectionProducts
disabled={disabled}
collection={collection}
{...collectionProductsProps}
/>
<CardSpacer />
<SeoForm
description={data.seoDescription}
disabled={disabled}
descriptionPlaceholder=""
helperText={intl.formatMessage({
defaultMessage:
"Add search engine title and description to make this collection easier to find"
})}
errors={errors}
slug={data.slug}
slugPlaceholder={data.name}
title={data.seoTitle}
titlePlaceholder={maybe(() => collection.name)}
onChange={change}
/>
</div>
<div>
<div> <div>
<CollectionDetails <VisibilityCard
collection={collection}
data={data} data={data}
disabled={disabled}
errors={errors} errors={errors}
messages={{
hiddenLabel: intl.formatMessage({
defaultMessage: "Hidden",
description: "collection label"
}),
hiddenSecondLabel: intl.formatMessage(
{
defaultMessage: "will be visible from {date}",
description: "collection"
},
{
date: localizeDate(data.publicationDate, "L")
}
),
visibleLabel: intl.formatMessage({
defaultMessage: "Visible",
description: "collection label"
})
}}
onChange={change} onChange={change}
/> >
<CardSpacer /> <FormSpacer />
<CollectionImage <Hr />
data={data} <ControlledCheckbox
image={maybe(() => collection.backgroundImage)} name={"isFeatured" as keyof CollectionUpdateData}
onImageDelete={onImageDelete} label={intl.formatMessage({
onImageUpload={onImageUpload} defaultMessage: "Feature on Homepage",
onChange={change} description: "switch button"
/> })}
<CardSpacer /> checked={data.isFeatured}
<Metadata data={data} onChange={changeMetadata} />
<CardSpacer />
<CollectionProducts
disabled={disabled}
collection={collection}
{...collectionProductsProps}
/>
<CardSpacer />
<SeoForm
description={data.seoDescription}
disabled={disabled}
descriptionPlaceholder=""
helperText={intl.formatMessage({
defaultMessage:
"Add search engine title and description to make this collection easier to find"
})}
errors={errors}
slug={data.slug}
slugPlaceholder={data.name}
title={data.seoTitle}
titlePlaceholder={maybe(() => collection.name)}
onChange={change}
/>
</div>
<div>
<div>
<VisibilityCard
data={data}
errors={errors}
messages={{
hiddenLabel: intl.formatMessage({
defaultMessage: "Hidden",
description: "collection label"
}),
hiddenSecondLabel: intl.formatMessage(
{
defaultMessage: "will be visible from {date}",
description: "collection"
},
{
date: localizeDate(data.publicationDate, "L")
}
),
visibleLabel: intl.formatMessage({
defaultMessage: "Visible",
description: "collection label"
})
}}
onChange={change} onChange={change}
> disabled={disabled}
<FormSpacer /> />
<Hr /> </VisibilityCard>
<ControlledCheckbox
name={"isFeatured" as keyof CollectionDetailsPageFormData}
label={intl.formatMessage({
defaultMessage: "Feature on Homepage",
description: "switch button"
})}
checked={data.isFeatured}
onChange={change}
disabled={disabled}
/>
</VisibilityCard>
</div>
</div> </div>
</Grid> </div>
<SaveButtonBar </Grid>
state={saveButtonBarState} <SaveButtonBar
disabled={disabled || !hasChanged} state={saveButtonBarState}
onCancel={onBack} disabled={disabled || !hasChanged}
onDelete={onCollectionRemove} onCancel={onBack}
onSave={submit} onDelete={onCollectionRemove}
/> onSave={submit}
</Container> />
); </Container>
}} )}
</Form> </CollectionUpdateForm>
); );
}; };
CollectionDetailsPage.displayName = "CollectionDetailsPage"; CollectionDetailsPage.displayName = "CollectionDetailsPage";

View file

@ -0,0 +1,121 @@
import { OutputData } from "@editorjs/editorjs";
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
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 CollectionUpdateFormData extends MetadataFormData {
backgroundImageAlt: string;
name: string;
slug: string;
publicationDate: string;
seoDescription: string;
seoTitle: string;
isFeatured: boolean;
isPublished: boolean;
}
export interface CollectionUpdateData extends CollectionUpdateFormData {
description: OutputData;
}
interface CollectionUpdateHandlers {
changeMetadata: FormChange;
changeDescription: RichTextEditorChange;
}
export interface UseCollectionUpdateFormResult {
change: FormChange;
data: CollectionUpdateData;
handlers: CollectionUpdateHandlers;
hasChanged: boolean;
submit: () => Promise<boolean>;
}
export interface CollectionUpdateFormProps {
children: (props: UseCollectionUpdateFormResult) => React.ReactNode;
collection: CollectionDetails_collection;
isFeatured: boolean;
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
}
function useCollectionUpdateForm(
collection: CollectionDetails_collection,
onSubmit: (data: CollectionUpdateData) => Promise<any[]>,
isFeatured: boolean
): UseCollectionUpdateFormResult {
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
const form = useForm<CollectionUpdateFormData>({
backgroundImageAlt: collection?.backgroundImage?.alt || "",
isFeatured,
isPublished: !!collection?.isPublished,
metadata: collection?.metadata?.map(mapMetadataItemToInput),
name: collection?.name || "",
privateMetadata: collection?.privateMetadata?.map(mapMetadataItemToInput),
publicationDate: collection?.publicationDate || "",
seoDescription: collection?.seoDescription || "",
seoTitle: collection?.seoTitle || "",
slug: collection?.slug || ""
});
const [description, changeDescription] = useRichText({
initial: collection?.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 = (): CollectionUpdateData => ({
...form.data,
description: description.current,
isPublished: form.data.isPublished || !!form.data.publicationDate
});
const getSubmitData = (): CollectionUpdateData => ({
...getData(),
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
});
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
return {
change: handleChange,
data: getData(),
handlers: {
changeDescription,
changeMetadata
},
hasChanged: changed,
submit
};
}
const CollectionUpdateForm: React.FC<CollectionUpdateFormProps> = ({
children,
collection,
isFeatured,
onSubmit
}) => {
const props = useCollectionUpdateForm(collection, onSubmit, isFeatured);
return <form onSubmit={props.submit}>{children(props)}</form>;
};
CollectionUpdateForm.displayName = "CollectionUpdateForm";
export default CollectionUpdateForm;