From 4801cf3a131adf57812814707f2583c8ffe13b1e Mon Sep 17 00:00:00 2001 From: lnn0q Date: Sun, 1 Jun 2025 00:37:26 +0300 Subject: [PATCH] added chapter view, fixes, added ratings, added chapter updates/deletions --- app/client/components/BookChapterCard.tsx | 97 +++++++++++ app/client/components/BookChapterForm.tsx | 121 +++++++++++++ app/client/components/BookSearchList.tsx | 6 +- app/client/components/EditorSlate.tsx | 125 +++++++------- app/client/components/WorkingListCard.tsx | 3 +- app/client/hooks/useToastMutation.tsx | 13 +- app/client/pages/__root.tsx | 46 +++-- app/client/pages/editor.tsx | 4 +- app/client/pages/new/book.tsx | 6 +- app/client/pages/register.tsx | 4 +- .../book/$bookId/chapter/$chapterId/edit.tsx | 104 ++++++++++++ .../book/$bookId/chapter/$chapterId/view.tsx | 160 ++++++++++++++++++ .../pages/work/book/$bookId/chapter/new.tsx | 89 ++++++++++ .../{$bookId.tsx => $bookId/overview.tsx} | 141 ++++++++++----- app/client/routeTree.gen.ts | 122 ++++++++++--- app/server/graphql/graphql.ts | 8 +- .../resolvers/bookChapterCreateResolver.ts | 38 +++++ .../graphql/resolvers/bookChapterDelete.ts | 25 +++ .../resolvers/bookChapterManyResolver.ts | 9 +- .../resolvers/bookChapterOneResolver.ts | 4 +- .../resolvers/bookChapterUpdateResolver.ts | 34 ++++ .../graphql/resolvers/bookCreateResolver.ts | 5 +- .../graphql/resolvers/bookManyResolver.ts | 9 +- .../graphql/resolvers/bookOneResolver.ts | 4 +- .../resolvers/mappers/bookChapterMapper.ts | 69 +++++++- .../graphql/resolvers/mappers/bookMapper.ts | 22 ++- .../graphql/resolvers/ratingSetResolver.ts | 102 +++++++++++ app/server/graphql/schema.graphql | 65 ++++++- app/server/helpers.ts | 27 +++ app/server/main.ts | 2 + app/server/prisma/schema.prisma | 24 ++- app/server/service/book.ts | 26 +++ package-lock.json | 16 ++ package.json | 6 +- 34 files changed, 1345 insertions(+), 191 deletions(-) create mode 100644 app/client/components/BookChapterCard.tsx create mode 100644 app/client/components/BookChapterForm.tsx create mode 100644 app/client/pages/work/book/$bookId/chapter/$chapterId/edit.tsx create mode 100644 app/client/pages/work/book/$bookId/chapter/$chapterId/view.tsx create mode 100644 app/client/pages/work/book/$bookId/chapter/new.tsx rename app/client/pages/work/book/{$bookId.tsx => $bookId/overview.tsx} (54%) create mode 100644 app/server/graphql/resolvers/bookChapterDelete.ts create mode 100644 app/server/graphql/resolvers/bookChapterUpdateResolver.ts create mode 100644 app/server/graphql/resolvers/ratingSetResolver.ts create mode 100644 app/server/service/book.ts 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 ( +
+ + + + + + + + + + + + + + + + + +