diff --git a/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx b/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx index 777ef78c7..a54235658 100644 --- a/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx +++ b/src/collections/components/CollectionCreatePage/CollectionCreatePage.tsx @@ -2,9 +2,8 @@ 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 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 SaveButtonBar from "@saleor/components/SaveButtonBar"; import SeoForm from "@saleor/components/SeoForm"; @@ -12,54 +11,21 @@ import VisibilityCard from "@saleor/components/VisibilityCard"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import useDateLocalize from "@saleor/hooks/useDateLocalize"; 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 CollectionDetails from "../CollectionDetails/CollectionDetails"; import { CollectionImage } from "../CollectionImage/CollectionImage"; - -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; -} +import CollectionCreateForm, { CollectionCreateData } from "./form"; export interface CollectionCreatePageProps { disabled: boolean; errors: ProductErrorFragment[]; saveButtonBarState: ConfirmButtonTransitionState; onBack: () => void; - onSubmit: (data: CollectionCreatePageFormData) => void; + onSubmit: (data: CollectionCreateData) => Promise; } -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 = ({ disabled, errors, @@ -69,127 +35,121 @@ const CollectionCreatePage: React.FC = ({ }: CollectionCreatePageProps) => { const intl = useIntl(); const localizeDate = useDateLocalize(); - const { - makeChangeHandler: makeMetadataChangeHandler - } = useMetadataChangeTrigger(); return ( -
- {({ change, data, hasChanged, submit }) => { - const changeMetadata = makeMetadataChangeHandler(change); - - return ( - - - {intl.formatMessage(sectionNames.collections)} - - - -
- - - - change({ - target: { - name: "backgroundImage", - value: { - url: null, - value: null - } + + {({ change, data, handlers, hasChanged, submit }) => ( + + + {intl.formatMessage(sectionNames.collections)} + + + +
+ + + - change({ - target: { - name: "backgroundImage", - value: { - url: URL.createObjectURL(file), - value: file - } + : null + } + onImageDelete={() => + change({ + target: { + name: "backgroundImage", + value: { + url: null, + value: null } - } as any) - } - onChange={change} - data={data} - /> - - - - -
-
- + change({ + target: { + name: "backgroundImage", + value: { + url: URL.createObjectURL(file), + value: file } - ), - visibleLabel: intl.formatMessage({ - defaultMessage: "Visible", - description: "collection label" - }) - }} - onChange={change} - /> -
-
- -
- ); - }} - + } + } as any) + } + onChange={change} + data={data} + /> + + + + +
+
+ +
+
+ +
+ )} + ); }; CollectionCreatePage.displayName = "CollectionCreatePage"; diff --git a/src/collections/components/CollectionCreatePage/form.tsx b/src/collections/components/CollectionCreatePage/form.tsx new file mode 100644 index 000000000..4a49c03cf --- /dev/null +++ b/src/collections/components/CollectionCreatePage/form.tsx @@ -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; +} + +export interface CollectionCreateFormProps { + children: (props: UseCollectionCreateFormResult) => React.ReactNode; + onSubmit: (data: CollectionCreateData) => Promise; +} + +function useCollectionCreateForm( + onSubmit: (data: CollectionCreateData) => Promise +): UseCollectionCreateFormResult { + const [changed, setChanged] = React.useState(false); + const triggerChange = () => setChanged(true); + + const form = useForm({ + 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 = ({ + children, + onSubmit +}) => { + const props = useCollectionCreateForm(onSubmit); + + return
{children(props)}
; +}; + +CollectionCreateForm.displayName = "CollectionCreateForm"; +export default CollectionCreateForm; diff --git a/src/collections/components/CollectionDetails/CollectionDetails.tsx b/src/collections/components/CollectionDetails/CollectionDetails.tsx index 2de3ae015..90187bcd0 100644 --- a/src/collections/components/CollectionDetails/CollectionDetails.tsx +++ b/src/collections/components/CollectionDetails/CollectionDetails.tsx @@ -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 { maybe } from "@saleor/misc"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; -import { RawDraftContentState } from "draft-js"; import React from "react"; import { useIntl } from "react-intl"; -import { CollectionDetails_collection } from "../../types/CollectionDetails"; - export interface CollectionDetailsProps { - collection?: CollectionDetails_collection; data: { - description: RawDraftContentState; + description: OutputData; name: string; }; disabled: boolean; errors: ProductErrorFragment[]; onChange: (event: React.ChangeEvent) => void; + onDescriptionChange: RichTextEditorChange; } const CollectionDetails: React.FC = ({ - collection, disabled, data, onChange, + onDescriptionChange, errors }) => { const intl = useIntl(); @@ -57,13 +56,13 @@ const CollectionDetails: React.FC = ({ /> JSON.parse(collection.descriptionJson))} label={intl.formatMessage(commonMessages.description)} name="description" disabled={disabled} - onChange={onChange} + onChange={onDescriptionChange} /> diff --git a/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx b/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx index 0b37e6e51..4728cec71 100644 --- a/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx +++ b/src/collections/components/CollectionDetailsPage/CollectionDetailsPage.tsx @@ -3,12 +3,10 @@ import { CardSpacer } from "@saleor/components/CardSpacer"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { Container } from "@saleor/components/Container"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; -import Form from "@saleor/components/Form"; import FormSpacer from "@saleor/components/FormSpacer"; import Grid from "@saleor/components/Grid"; import Hr from "@saleor/components/Hr"; 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"; @@ -16,9 +14,6 @@ import VisibilityCard from "@saleor/components/VisibilityCard"; import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment"; import useDateLocalize from "@saleor/hooks/useDateLocalize"; 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 { useIntl } from "react-intl"; @@ -28,18 +23,7 @@ import { CollectionDetails_collection } from "../../types/CollectionDetails"; import CollectionDetails from "../CollectionDetails/CollectionDetails"; import { CollectionImage } from "../CollectionImage/CollectionImage"; import CollectionProducts from "../CollectionProducts/CollectionProducts"; - -export interface CollectionDetailsPageFormData extends MetadataFormData { - backgroundImageAlt: string; - description: RawDraftContentState; - name: string; - slug: string; - publicationDate: string; - seoDescription: string; - seoTitle: string; - isFeatured: boolean; - isPublished: boolean; -} +import CollectionUpdateForm, { CollectionUpdateData } from "./form"; export interface CollectionDetailsPageProps extends PageListProps, ListActions { collection: CollectionDetails_collection; @@ -51,7 +35,7 @@ export interface CollectionDetailsPageProps extends PageListProps, ListActions { onImageDelete: () => void; onImageUpload: (file: File) => void; onProductUnassign: (id: string, event: React.MouseEvent) => void; - onSubmit: (data: CollectionDetailsPageFormData) => void; + onSubmit: (data: CollectionUpdateData) => Promise; } const CollectionDetailsPage: React.FC = ({ @@ -69,150 +53,113 @@ const CollectionDetailsPage: React.FC = ({ }: CollectionDetailsPageProps) => { const intl = useIntl(); 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 ( -
collection.backgroundImage.alt, ""), - description: maybe(() => JSON.parse(collection.descriptionJson)), - 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 }) => { - const changeMetadata = makeMetadataChangeHandler(change); - - return ( - - - {intl.formatMessage(sectionNames.collections)} - - collection.name)} /> - + {({ change, data, handlers, hasChanged, submit }) => ( + + + {intl.formatMessage(sectionNames.collections)} + + collection.name)} /> + +
+ + + collection.backgroundImage)} + onImageDelete={onImageDelete} + onImageUpload={onImageUpload} + onChange={change} + /> + + + + + + collection.name)} + onChange={change} + /> +
+
- - - collection.backgroundImage)} - onImageDelete={onImageDelete} - onImageUpload={onImageUpload} - onChange={change} - /> - - - - - - collection.name)} - onChange={change} - /> -
-
-
- + +
+ - -
- -
-
+ disabled={disabled} + /> +
- - - - ); - }} - +
+
+ +
+ )} +
); }; CollectionDetailsPage.displayName = "CollectionDetailsPage"; diff --git a/src/collections/components/CollectionDetailsPage/form.tsx b/src/collections/components/CollectionDetailsPage/form.tsx new file mode 100644 index 000000000..e5cb9bfc7 --- /dev/null +++ b/src/collections/components/CollectionDetailsPage/form.tsx @@ -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; +} + +export interface CollectionUpdateFormProps { + children: (props: UseCollectionUpdateFormResult) => React.ReactNode; + collection: CollectionDetails_collection; + isFeatured: boolean; + onSubmit: (data: CollectionUpdateData) => Promise; +} + +function useCollectionUpdateForm( + collection: CollectionDetails_collection, + onSubmit: (data: CollectionUpdateData) => Promise, + isFeatured: boolean +): UseCollectionUpdateFormResult { + const [changed, setChanged] = React.useState(false); + const triggerChange = () => setChanged(true); + + const form = useForm({ + 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 = ({ + children, + collection, + isFeatured, + onSubmit +}) => { + const props = useCollectionUpdateForm(collection, onSubmit, isFeatured); + + return
{children(props)}
; +}; + +CollectionUpdateForm.displayName = "CollectionUpdateForm"; +export default CollectionUpdateForm;