ln/app/client/components/BookChapterForm.tsx

121 lines
3.5 KiB
TypeScript

import { Button } from '@app/client/components/ui/button';
import { Box, Input, Stack, Textarea } from '@chakra-ui/react';
import { valibotResolver } from '@hookform/resolvers/valibot';
import { Link } from '@tanstack/react-router';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { Descendant } from 'slate';
import * as v from 'valibot';
import { EditorSlate } from './EditorSlate';
import { Field } from './ui/field';
interface BookChapterEditInitialValues {
readonly title: string;
readonly foreword?: string;
readonly footnote?: string;
readonly content?: Descendant[];
}
interface BookChapterInput {
readonly title: string;
readonly foreword?: string;
readonly footnote?: string;
readonly content: Descendant[];
}
interface BookChapterFormValues {
readonly title: string;
readonly foreword: string;
readonly footnote: string;
}
export function BookChapterForm({
bookId,
initialValues,
onChapterSubmit
}: {
readonly bookId: string;
readonly initialValues?: BookChapterEditInitialValues | null;
readonly onChapterSubmit: (chapter: BookChapterInput) => Promise<void>;
}) {
const [content, setContent] = useState<Descendant[]>([]);
const schema = v.pipe(
v.object({
title: v.pipe(v.string(), v.nonEmpty('Title is required'), v.maxLength(60)),
foreword: v.pipe(v.string()),
footnote: v.pipe(v.string())
})
);
const {
register,
handleSubmit,
formState: { errors }
} = useForm<BookChapterFormValues>({
defaultValues: {
title: initialValues?.title,
foreword: initialValues?.foreword,
footnote: initialValues?.footnote
},
resolver: valibotResolver(schema)
});
const onSubmit = handleSubmit(async values =>
onChapterSubmit({
title: values.title,
foreword: values.foreword,
footnote: values.footnote,
content
})
);
return (
<form onSubmit={onSubmit}>
<Stack justifyContent="center" alignItems="center" minHeight="88vh">
<Stack maxWidth="840px" minHeight="80vh" justifyContent="center" alignItems="center">
<Stack width="100%" direction="row" justifyContent="space-between" alignItems="start">
<Box>
<Field invalid={errors.title != null} errorText={errors.title?.message}>
<Input {...register('title')} placeholder="Chapter title" />
</Field>
</Box>
<Stack direction="row">
<Button onClick={async () => onSubmit()}>Submit</Button>
<Link to={`/work/book/${bookId}/overview`}>
<Button>Go back</Button>
</Link>
</Stack>
</Stack>
<Field invalid={errors.foreword != null} errorText={errors.foreword?.message}>
<Textarea
color="fg.muted"
variant="subtle"
placeholder="Foreword"
resize="none"
maxLength={500}
{...register('foreword')}
/>
</Field>
<EditorSlate
initialValue={initialValues?.content}
onChange={values => setContent(values)}
/>
<Field invalid={errors.footnote != null} errorText={errors.footnote?.message}>
<Textarea
color="fg.muted"
variant="subtle"
placeholder="Footnote"
resize="none"
maxLength={500}
{...register('footnote')}
/>
</Field>
</Stack>
</Stack>
</form>
);
}