diff --git a/meta.ts b/meta.ts index 83e784ab..716eaff8 100644 --- a/meta.ts +++ b/meta.ts @@ -51,6 +51,7 @@ export const meta = { preview: `/document-preview/`, management: `/documents/`, }, + markdownPostCreator: `/markdown-post-creator/`, notFound: `/404/`, privacyPolicy: `/privacy-policy/`, auth: { diff --git a/src/acts/create-markdown-post.act.ts b/src/acts/create-markdown-post.act.ts new file mode 100644 index 00000000..0443de19 --- /dev/null +++ b/src/acts/create-markdown-post.act.ts @@ -0,0 +1,43 @@ +import { getAPI, parseError, setCache } from "api-4markdown"; +import type { Atoms } from "api-4markdown-contracts"; +import type { AsyncResult } from "development-kit/utility-types"; +import { docManagementStoreActions } from "store/doc-management/doc-management.store"; +import { docStoreActions } from "store/doc/doc.store"; +import { docsStoreActions, docsStoreSelectors } from "store/docs/docs.store"; +import { useMarkdownPostCreatorState } from "store/markdown-post-creator"; +import { resetAction } from "store/markdown-post-creator/actions"; + +const createMarkdownPostAct = async (): AsyncResult<{ + id: Atoms["DocumentId"]; +}> => { + const { title, content } = useMarkdownPostCreatorState.get(); + + try { + docManagementStoreActions.busy(); + + const createdDocument = await getAPI().call(`createDocument`)({ + name: title, + code: content, + }); + + docManagementStoreActions.ok(); + docStoreActions.setActive(createdDocument); + + const docsState = docsStoreSelectors.state(); + + if (docsState.is === `ok`) { + docsStoreActions.addDoc(createdDocument); + setCache(`getYourDocuments`, docsStoreSelectors.ok().docs); + } + + resetAction(); + + return { is: `ok`, data: { id: createdDocument.id } }; + } catch (error: unknown) { + docManagementStoreActions.fail(error); + + return { is: `fail`, error: parseError(error) }; + } +}; + +export { createMarkdownPostAct }; diff --git a/src/features/markdown-post-creator/containers/create-post-form.container.tsx b/src/features/markdown-post-creator/containers/create-post-form.container.tsx new file mode 100644 index 00000000..4e2c6026 --- /dev/null +++ b/src/features/markdown-post-creator/containers/create-post-form.container.tsx @@ -0,0 +1,126 @@ +import React from "react"; +import { navigate } from "gatsby"; +import { Button } from "design-system/button"; +import { Field } from "design-system/field"; +import { Input } from "design-system/input"; +import { useForm } from "development-kit/use-form"; +import type { ValidatorFn, ValidatorsSetup } from "development-kit/form"; +import { useDocManagementStore } from "store/doc-management/doc-management.store"; +import { + changeContentAction, + changeTitleAction, +} from "store/markdown-post-creator/actions"; +import { useMarkdownPostCreatorState } from "store/markdown-post-creator"; +import { createMarkdownPostAct } from "acts/create-markdown-post.act"; +import { meta } from "../../../../meta"; + +type FormValues = { + title: string; +}; + +const titleMinLength = 3; +const titleMaxLength = 100; + +const titleValidator: ValidatorFn = (value) => { + const trimmed = value.trim(); + + if (trimmed.length < titleMinLength) { + return `Title must be at least ${titleMinLength} characters`; + } + + if (trimmed.length > titleMaxLength) { + return `Title must be fewer than ${titleMaxLength} characters`; + } + + return null; +}; + +const validators: ValidatorsSetup = { + title: [titleValidator], +}; + +const CreatePostFormContainer = () => { + const docManagementStore = useDocManagementStore(); + const { title, content } = useMarkdownPostCreatorState(); + const [{ invalid, untouched, result }, { inject }] = useForm( + { title }, + validators, + ); + + const disabled = docManagementStore.is === `busy`; + + const handleTitleChange: React.ChangeEventHandler = (e) => { + inject(`title`).onChange(e); + changeTitleAction(e.target.value); + }; + + const handleContentChange: React.ChangeEventHandler = ( + e, + ) => { + changeContentAction(e.target.value); + }; + + const handleSubmit = async (): Promise => { + const result = await createMarkdownPostAct(); + + if (result.is === `ok`) { + void navigate(`${meta.routes.home}?id=${result.data.id}`); + } + }; + + return ( +
+ } + hint={ + result.title ? ( + {result.title} + ) : ( + + {titleMinLength}–{titleMaxLength} characters + + ) + } + > + + + +