added chapter view, fixes, added ratings, added chapter updates/deletions

This commit is contained in:
lnn0q 2025-06-01 00:37:26 +03:00
parent 029b7e9353
commit 4801cf3a13
34 changed files with 1345 additions and 191 deletions

View file

@ -0,0 +1,104 @@
import { gql, useQuery } from '@apollo/client';
import { BookChapterForm } from '@app/client/components/BookChapterForm';
import { useIdentity } from '@app/client/hooks/identity';
import { useToastMutation } from '@app/client/hooks/useToastMutation';
import { Skeleton } from '@chakra-ui/react';
import { BookChapterEditQuery, BookChapterNewCreateMutation } from '@generated/types';
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';
export const Route = createFileRoute('/work/book/$bookId/chapter/$chapterId/edit')({
component: RouteComponent,
beforeLoad: ({ context }) => {
if (!context.isAuthenticated) throw redirect({ to: '/' });
}
});
function RouteComponent() {
const { bookId, chapterId } = Route.useParams();
const identity = useIdentity();
const navigate = useNavigate();
const { data } = useQuery<BookChapterEditQuery>(query, {
variables: { id: chapterId }
});
const { update: chapterUpdate } = useToastMutation<BookChapterNewCreateMutation>({
mutation,
successMsg: 'Chapter updated successfully'
});
if (data == null) return <Skeleton />;
if (identity?.data.user.id !== data.bookChapterOne.book.author.id)
throw redirect({ to: `/work/book/${bookId}/overview` });
return (
<BookChapterForm
bookId={bookId}
initialValues={{
title: data.bookChapterOne.title,
foreword: data.bookChapterOne.foreword != null ? data.bookChapterOne.foreword : undefined,
footnote: data.bookChapterOne.footnote != null ? data.bookChapterOne.footnote : undefined,
content: data.bookChapterOne.content
}}
onChapterSubmit={async chapter => {
await chapterUpdate({
variables: {
input: {
id: chapterId,
...chapter
}
}
});
navigate({ to: `/work/book/${bookId}/overview` });
}}
/>
);
}
const query = gql`
query BookChapterEdit($id: ID!) {
bookChapterOne(id: $id) {
id
title
foreword
footnote
content
previousChapterId
nextChapterId
book {
id
updatedAt
wordsTotal
chaptersTotal
author {
id
}
}
}
}
`;
const mutation = gql`
mutation BookChapterEditUpdate($input: BookChapterUpdateInput!) {
bookChapterUpdate(input: $input) {
id
title
foreword
footnote
content
previousChapterId
nextChapterId
book {
id
updatedAt
wordsTotal
chaptersTotal
author {
id
}
}
}
}
`;

View file

@ -0,0 +1,160 @@
import { gql, useQuery } from '@apollo/client';
import { EditorSlate } from '@app/client/components/EditorSlate';
import { bookChapterFragment } from '@app/client/fragments';
import { Button, Heading, Icon, Skeleton, Stack, Text } from '@chakra-ui/react';
import { BookChapterViewQuery, BookChapterViewNavigationQuery } from '@generated/types';
import { createFileRoute, Link } from '@tanstack/react-router';
import { Descendant } from 'slate';
export const Route = createFileRoute('/work/book/$bookId/chapter/$chapterId/view')({
component: RouteComponent
});
function RouteComponent() {
const { bookId, chapterId } = Route.useParams();
const { data } = useQuery<BookChapterViewQuery>(bookChapterQuery, {
variables: { id: chapterId }
});
const { data: navigationData } = useQuery<BookChapterViewNavigationQuery>(
bookChapterNavigationQuery,
{
variables: { id: chapterId }
}
);
if (data == null || navigationData == null) return <Skeleton />;
return (
<Stack justifyContent="center" alignItems="center">
<BookChapterView
bookId={bookId}
chapter={{ ...data.bookChapterOne, ...navigationData.bookChapterOne }}
/>
</Stack>
);
}
interface BookChapterViewChapter {
readonly id: string;
readonly title: string;
readonly createdAt: string;
readonly updatedAt: string;
readonly foreword?: string | null;
readonly footnote?: string | null;
readonly content: Descendant[];
readonly sequenceNumber: number;
readonly reviewsTotal: number;
readonly previousChapterId?: string | null;
readonly nextChapterId?: string | null;
readonly book: {
readonly title: string;
};
}
function BookChapterView({
bookId,
chapter
}: {
readonly bookId: string;
readonly chapter: BookChapterViewChapter;
}) {
return (
<Stack
maxWidth="840px"
width="100%"
minHeight="80vh"
justifyContent="center"
alignItems="center"
>
<Heading as="div" maxWidth="100%">
<Text textAlign="center" minWidth={0}>
{chapter.book.title}
</Text>
<Text textAlign="center">-</Text>
<Text textAlign="center" minWidth={0}>
{chapter.title}
</Text>
</Heading>
<EditorSlate initialValue={chapter.content} readOnly />
<ChapterNavigation
bookId={bookId}
previousChapterId={chapter.previousChapterId}
nextChapterId={chapter.nextChapterId}
/>
</Stack>
);
}
function ChapterNavigation({
bookId,
previousChapterId,
nextChapterId
}: {
readonly bookId: string;
readonly previousChapterId?: string | null;
readonly nextChapterId?: string | null;
}) {
return (
<Stack direction="row" gap={0}>
{previousChapterId && (
<Link to={`/work/book/${bookId}/chapter/${previousChapterId}/view`}>
<Button variant="ghost" borderRightRadius={0}>
<Icon>
<i className="nf nf-md-page_previous" />
</Icon>
<Text>Prev</Text>
</Button>
</Link>
)}
<Link to={`/work/book/${bookId}/overview`}>
<Button
variant="ghost"
borderLeftRadius={previousChapterId ? 0 : undefined}
borderRightRadius={nextChapterId ? 0 : undefined}
>
<Icon>
<i className="nf nf-fa-table_list" />
</Icon>
<Text>Contents</Text>
</Button>
</Link>
{nextChapterId && (
<Link to={`/work/book/${bookId}/chapter/${nextChapterId}/view`}>
<Button variant="ghost" borderLeftRadius={0}>
<Text>Next</Text>
<Icon>
<i className="nf nf-md-page_next" />
</Icon>
</Button>
</Link>
)}
</Stack>
);
}
const bookChapterQuery = gql`
query BookChapterView($id: ID!) {
bookChapterOne(id: $id) {
id
content
...BookChapter
book {
title
chaptersTotal
}
}
}
${bookChapterFragment}
`;
export const bookChapterNavigationQuery = gql`
query BookChapterViewNavigation($id: ID!) {
bookChapterOne(id: $id) {
id
previousChapterId
nextChapterId
}
}
`;