diff --git a/app/client/components/BookChapterCard.tsx b/app/client/components/BookChapterCard.tsx new file mode 100644 index 0000000..0716ec2 --- /dev/null +++ b/app/client/components/BookChapterCard.tsx @@ -0,0 +1,97 @@ +import { gql } from '@apollo/client'; +import { Box, Button, Card, Heading, Icon, Stack, Text } from '@chakra-ui/react'; +import { BookChapterDeleteMutation } from '@generated/types'; +import { Link } from '@tanstack/react-router'; +import { bookFragment } from '../fragments'; +import { useToastMutation } from '../hooks/useToastMutation'; +import { bookChapterListQuery } from '../pages/work/book/$bookId/overview'; + +export function BookChapterCard({ + bookId, + chapterId, + title, + isAuthor +}: { + readonly bookId: string; + readonly chapterId: string; + readonly title: string; + readonly isAuthor: boolean; +}) { + const { update: chapterDelete } = useToastMutation({ + mutation, + successMsg: 'Chapter deleted successfully', + refetchQueries: [bookChapterListQuery] + }); + return ( + + + + + + + + {title} + + + + + + {isAuthor && ( + <> + + + + + + )} + + + + + + + + ); +} + +const mutation = gql` + mutation BookChapterDelete($id: ID!) { + bookChapterDelete(id: $id) { + ...Book + } + } + ${bookFragment} +`; diff --git a/app/client/components/BookChapterForm.tsx b/app/client/components/BookChapterForm.tsx new file mode 100644 index 0000000..66f242e --- /dev/null +++ b/app/client/components/BookChapterForm.tsx @@ -0,0 +1,121 @@ +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; +}) { + const [content, setContent] = useState([]); + + 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({ + 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 ( +
+ + + + + + + + + + + + + + + + + +