implemented ratings, fixes
This commit is contained in:
parent
4801cf3a13
commit
d1da980501
31 changed files with 775 additions and 236 deletions
|
|
@ -51,7 +51,7 @@ function RouteComponent() {
|
|||
}
|
||||
});
|
||||
|
||||
navigate({ to: `/work/book/${bookId}/overview` });
|
||||
navigate({ to: `/work/book/${bookId}/overview/contents` });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
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 { Button, Heading, Icon, Separator, Skeleton, Stack, Text } from '@chakra-ui/react';
|
||||
import { BookChapterViewNavigationQuery, BookChapterViewQuery } 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
|
||||
|
|
@ -13,53 +12,35 @@ export const Route = createFileRoute('/work/book/$bookId/chapter/$chapterId/view
|
|||
function RouteComponent() {
|
||||
const { bookId, chapterId } = Route.useParams();
|
||||
|
||||
return (
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<BookChapterView bookId={bookId} chapterId={chapterId} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function BookChapterView({
|
||||
bookId,
|
||||
chapterId
|
||||
}: {
|
||||
readonly bookId: string;
|
||||
readonly chapterId: string;
|
||||
}) {
|
||||
const { data } = useQuery<BookChapterViewQuery>(bookChapterQuery, {
|
||||
variables: { id: chapterId }
|
||||
variables: { id: chapterId },
|
||||
fetchPolicy: 'cache-and-network'
|
||||
});
|
||||
|
||||
const { data: navigationData } = useQuery<BookChapterViewNavigationQuery>(
|
||||
bookChapterNavigationQuery,
|
||||
{
|
||||
variables: { id: chapterId }
|
||||
variables: { id: chapterId },
|
||||
fetchPolicy: 'no-cache'
|
||||
}
|
||||
);
|
||||
|
||||
if (data == null || navigationData == null) return <Skeleton />;
|
||||
if (data == 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"
|
||||
|
|
@ -70,18 +51,38 @@ function BookChapterView({
|
|||
>
|
||||
<Heading as="div" maxWidth="100%">
|
||||
<Text textAlign="center" minWidth={0}>
|
||||
{chapter.book.title}
|
||||
{data.bookChapterOne.book.title}
|
||||
</Text>
|
||||
<Text textAlign="center">-</Text>
|
||||
<Text textAlign="center" minWidth={0}>
|
||||
{chapter.title}
|
||||
{data.bookChapterOne.title}
|
||||
</Text>
|
||||
</Heading>
|
||||
<EditorSlate initialValue={chapter.content} readOnly />
|
||||
{data.bookChapterOne.foreword && (
|
||||
<>
|
||||
<Stack width="91%" justifyContent="left" alignItems="start" paddingY={2}>
|
||||
<Text fontWeight="bold">Foreword:</Text>
|
||||
<Text color="fg.muted">{data.bookChapterOne.foreword}</Text>
|
||||
</Stack>
|
||||
<Separator width="91%" />
|
||||
</>
|
||||
)}
|
||||
<EditorSlate initialValue={data.bookChapterOne.content} readOnly />
|
||||
{data.bookChapterOne.footnote && (
|
||||
<>
|
||||
<Separator width="91%" />
|
||||
<Stack width="91%" justifyContent="left" alignItems="start" paddingY={2}>
|
||||
<Text fontWeight="bold">Footnote:</Text>
|
||||
<Text color="fg.muted">{data.bookChapterOne.foreword}</Text>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text>{data.bookChapterOne.footnote}</Text>
|
||||
<ChapterNavigation
|
||||
bookId={bookId}
|
||||
previousChapterId={chapter.previousChapterId}
|
||||
nextChapterId={chapter.nextChapterId}
|
||||
previousChapterId={navigationData?.bookChapterOne.previousChapterId}
|
||||
nextChapterId={navigationData?.bookChapterOne.nextChapterId}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
|
@ -108,7 +109,7 @@ function ChapterNavigation({
|
|||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Link to={`/work/book/${bookId}/overview`}>
|
||||
<Link to={`/work/book/${bookId}/overview/contents`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
borderLeftRadius={previousChapterId ? 0 : undefined}
|
||||
|
|
@ -134,7 +135,7 @@ function ChapterNavigation({
|
|||
);
|
||||
}
|
||||
|
||||
const bookChapterQuery = gql`
|
||||
export const bookChapterQuery = gql`
|
||||
query BookChapterView($id: ID!) {
|
||||
bookChapterOne(id: $id) {
|
||||
id
|
||||
|
|
@ -149,7 +150,7 @@ const bookChapterQuery = gql`
|
|||
${bookChapterFragment}
|
||||
`;
|
||||
|
||||
export const bookChapterNavigationQuery = gql`
|
||||
const bookChapterNavigationQuery = gql`
|
||||
query BookChapterViewNavigation($id: ID!) {
|
||||
bookChapterOne(id: $id) {
|
||||
id
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import { useToastMutation } from '@app/client/hooks/useToastMutation';
|
|||
import { Skeleton } from '@chakra-ui/react';
|
||||
import { BookChapterNewCreateMutation, BookChapterNewQuery } from '@generated/types';
|
||||
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';
|
||||
import { bookChapterListQuery } from '../overview';
|
||||
import { bookChapterNavigationQuery } from './$chapterId/view';
|
||||
import { bookChapterListQuery } from '../overview/contents';
|
||||
|
||||
export const Route = createFileRoute('/work/book/$bookId/chapter/new')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -28,12 +27,12 @@ function RouteComponent() {
|
|||
const { update: chapterCreate } = useToastMutation<BookChapterNewCreateMutation>({
|
||||
mutation,
|
||||
successMsg: 'Chapter created successfully',
|
||||
refetchQueries: [bookChapterListQuery, bookChapterNavigationQuery]
|
||||
refetchQueries: [bookChapterListQuery]
|
||||
});
|
||||
|
||||
if (data == null) return <Skeleton />;
|
||||
if (identity?.data.user.id !== data.bookOne.author.id)
|
||||
throw redirect({ to: `/work/book/${id}/overview` });
|
||||
throw redirect({ to: `/work/book/${id}/overview/contents` });
|
||||
|
||||
return (
|
||||
<BookChapterForm
|
||||
|
|
@ -48,7 +47,7 @@ function RouteComponent() {
|
|||
}
|
||||
});
|
||||
|
||||
navigate({ to: `/work/book/${id}/overview` });
|
||||
navigate({ to: `/work/book/${id}/overview/contents` });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
import { gql, useQuery } from '@apollo/client';
|
||||
import { BookChapterCard } from '@app/client/components/BookChapterCard';
|
||||
import { PaginatedListSkeleton } from '@app/client/components/PaginatedListSkeleton';
|
||||
import { Pagination } from '@app/client/components/Pagination';
|
||||
import { Button } from '@app/client/components/ui/button';
|
||||
import { WorkingProgressStatusBadge } from '@app/client/components/WorkingProgressStatusBadge';
|
||||
import { WorkingRatingBadge } from '@app/client/components/WorkingRatingBadge';
|
||||
import { WorkingReviewsBadge } from '@app/client/components/WorkingReviewsTotalBadge';
|
||||
import { bookChapterFragment, bookFragment } from '@app/client/fragments';
|
||||
import { bookFragment } from '@app/client/fragments';
|
||||
import { toFormattedDate } from '@app/client/helpers';
|
||||
import { IdentityContext, useIdentity } from '@app/client/hooks/identity';
|
||||
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
|
||||
import { BookOne } from '@app/client/interfaces';
|
||||
import { Box, Heading, HStack, Icon, Skeleton, SkeletonText, Stack, Text } from '@chakra-ui/react';
|
||||
import { BookChapterListQuery, BookOverviewQuery } from '@generated/types';
|
||||
import { createFileRoute, Link } from '@tanstack/react-router';
|
||||
import { useState } from 'react';
|
||||
import { Heading, HStack, Skeleton, SkeletonText, Stack, Text } from '@chakra-ui/react';
|
||||
import { BookOverviewQuery } from '@generated/types';
|
||||
import { createFileRoute, Link, Outlet } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/work/book/$bookId/overview')({
|
||||
component: RouteComponent
|
||||
|
|
@ -26,28 +19,16 @@ function RouteComponent() {
|
|||
variables: { id }
|
||||
});
|
||||
|
||||
const identity = useIdentity();
|
||||
|
||||
return (
|
||||
<Stack width="100%" justifyContent="center" alignItems="center" padding={3}>
|
||||
{bookData != null ? (
|
||||
<BookOverview identity={identity} book={bookData.bookOne} />
|
||||
) : (
|
||||
<BookOverviewSkeleton />
|
||||
)}
|
||||
{bookData != null ? <BookOverview book={bookData.bookOne} /> : <BookOverviewSkeleton />}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function BookOverview({
|
||||
identity,
|
||||
book
|
||||
}: {
|
||||
readonly identity: IdentityContext | null;
|
||||
readonly book: BookOne;
|
||||
}) {
|
||||
function BookOverview({ book }: { readonly book: BookOne }) {
|
||||
return (
|
||||
<Stack width="80%" maxWidth="840px" gap={3}>
|
||||
<Stack width={{ base: '100%', md: '80%' }} maxWidth="840px" gap={3}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Heading color="colorPalette.400" size="2xl" overflow="auto">
|
||||
{book.title}
|
||||
|
|
@ -101,119 +82,14 @@ function BookOverview({
|
|||
|
||||
<Text>{book.description}</Text>
|
||||
|
||||
<BookChapterList
|
||||
id={book.id}
|
||||
identity={identity}
|
||||
isAuthor={identity?.data.id === book.author.id}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function BookChapterList({
|
||||
id,
|
||||
identity,
|
||||
isAuthor
|
||||
}: {
|
||||
readonly id: string;
|
||||
readonly identity: IdentityContext | null;
|
||||
readonly isAuthor: boolean;
|
||||
}) {
|
||||
const [chaptersListPage, setChaptersListPage] = useState<number>(1);
|
||||
const [chaptersListPageSize, setChaptersListPageSize] = useState<number>(10);
|
||||
const startIndex = (chaptersListPage - 1) * chaptersListPageSize;
|
||||
|
||||
const { data } = usePaginatedQuery<BookChapterListQuery>({
|
||||
query: bookChapterListQuery,
|
||||
input: {
|
||||
bookId: id,
|
||||
filter: {
|
||||
offset: startIndex,
|
||||
size: chaptersListPageSize
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{data != null ? (
|
||||
<>
|
||||
<Stack>
|
||||
{data.bookChapterMany.items.length > 0 ? (
|
||||
<>
|
||||
{data.bookChapterMany.items.map(item => (
|
||||
<Stack key={`bookChapterList-${item.id}`}>
|
||||
<BookChapterCard
|
||||
bookId={id}
|
||||
chapterId={item.id}
|
||||
title={item.title}
|
||||
isAuthor={isAuthor}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<Stack justifyContent="center" alignItems="center" pt={8}>
|
||||
<Text>No chapters here yet!</Text>
|
||||
{isAuthor && (
|
||||
<Link to={`/work/book/${id}/chapter/new`}>
|
||||
<Button variant="outline" size="sm">
|
||||
Create new chapter
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
{data.bookChapterMany.total > chaptersListPageSize && (
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<Pagination
|
||||
total={data.bookChapterMany.total}
|
||||
page={chaptersListPage}
|
||||
setPage={setChaptersListPage}
|
||||
pageSize={chaptersListPageSize}
|
||||
setPageSize={setChaptersListPageSize}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Stack direction="row">
|
||||
<>
|
||||
{isAuthor && data.bookChapterMany.items.length !== 0 && (
|
||||
<Box>
|
||||
<Link to={`/work/book/${id}/chapter/new`}>
|
||||
<Button size="sm">Create new chapter</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
|
||||
{identity != null && !isAuthor && (
|
||||
<>
|
||||
<Button size="sm" backgroundColor="green.500" disabled>
|
||||
<Icon>
|
||||
<i className="nf nf-oct-thumbsup" />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button size="sm" backgroundColor="red.500">
|
||||
<Icon>
|
||||
<i className="nf nf-oct-thumbsdown" />
|
||||
</Icon>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
) : (
|
||||
<PaginatedListSkeleton disableSearch />
|
||||
)}
|
||||
<Outlet />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function BookOverviewSkeleton() {
|
||||
return (
|
||||
<Stack width="80%" maxWidth="840px">
|
||||
<Stack width={{ base: '100%', md: '80%' }} maxWidth="840px">
|
||||
<Stack gap="6" maxWidth="100%">
|
||||
<HStack width="full">
|
||||
<SkeletonText noOfLines={5} />
|
||||
|
|
@ -224,7 +100,7 @@ function BookOverviewSkeleton() {
|
|||
);
|
||||
}
|
||||
|
||||
const bookQuery = gql`
|
||||
export const bookQuery = gql`
|
||||
query BookOverview($id: ID!) {
|
||||
bookOne(id: $id) {
|
||||
...Book
|
||||
|
|
@ -232,15 +108,3 @@ const bookQuery = gql`
|
|||
}
|
||||
${bookFragment}
|
||||
`;
|
||||
|
||||
export const bookChapterListQuery = gql`
|
||||
query BookChapterList($input: BookChapterManyInput!) {
|
||||
bookChapterMany(input: $input) {
|
||||
items {
|
||||
...BookChapter
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
${bookChapterFragment}
|
||||
`;
|
||||
|
|
|
|||
177
app/client/pages/work/book/$bookId/overview/contents.tsx
Normal file
177
app/client/pages/work/book/$bookId/overview/contents.tsx
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
import { gql, useMutation, useQuery } from '@apollo/client';
|
||||
import { BookChapterCard } from '@app/client/components/BookChapterCard';
|
||||
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
|
||||
import { PaginatedListSkeleton } from '@app/client/components/PaginatedListSkeleton';
|
||||
import { Pagination } from '@app/client/components/Pagination';
|
||||
import { RatingPanel } from '@app/client/components/RatingPanel';
|
||||
import { Button } from '@app/client/components/ui/button';
|
||||
import { bookChapterFragment } from '@app/client/fragments';
|
||||
import { useIdentity } from '@app/client/hooks/identity';
|
||||
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
|
||||
import { Box, Skeleton, Stack, Text } from '@chakra-ui/react';
|
||||
import { BookChapterListQuery, BookOverviewRatingQuery } from '@generated/types';
|
||||
import { createFileRoute, Link } from '@tanstack/react-router';
|
||||
import { useState } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/work/book/$bookId/overview/contents')({
|
||||
component: RouteComponent
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { bookId: id } = Route.useParams();
|
||||
|
||||
const identity = useIdentity();
|
||||
|
||||
const { data } = useQuery<BookOverviewRatingQuery>(bookRatingQuery, {
|
||||
variables: { id }
|
||||
});
|
||||
|
||||
const [ratingUpdate] = useMutation<BookOverviewRatingQuery>(bookRatingMutation, {
|
||||
refetchQueries: [bookRatingQuery]
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<BookOverviewTabs bookId={id} currentTab="contents" />
|
||||
|
||||
{data != null ? (
|
||||
<>
|
||||
<BookChapterList
|
||||
id={data.bookOne.id}
|
||||
isAuthor={identity?.data.id === data.bookOne.author.id}
|
||||
/>
|
||||
|
||||
{identity != null &&
|
||||
identity?.data.id !== data.bookOne.author.id &&
|
||||
data.bookOne.chaptersTotal > 0 && (
|
||||
<RatingPanel
|
||||
personalRating={data.bookOne.personalRating}
|
||||
onRatingSet={async ratingType => {
|
||||
await ratingUpdate({
|
||||
variables: {
|
||||
input: {
|
||||
bookId: data.bookOne.id,
|
||||
ratingType
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton height="200px" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function BookChapterList({ id, isAuthor }: { readonly id: string; readonly isAuthor: boolean }) {
|
||||
const [chaptersListPage, setChaptersListPage] = useState<number>(1);
|
||||
const [chaptersListPageSize, setChaptersListPageSize] = useState<number>(10);
|
||||
const startIndex = (chaptersListPage - 1) * chaptersListPageSize;
|
||||
|
||||
const { data } = usePaginatedQuery<BookChapterListQuery>({
|
||||
query: bookChapterListQuery,
|
||||
input: {
|
||||
bookId: id,
|
||||
filter: {
|
||||
offset: startIndex,
|
||||
size: chaptersListPageSize
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{data != null ? (
|
||||
<>
|
||||
<Stack>
|
||||
{data.bookChapterMany.items.length > 0 ? (
|
||||
<>
|
||||
{data.bookChapterMany.items.map(item => (
|
||||
<Stack key={`bookChapterList-${item.id}`}>
|
||||
<BookChapterCard
|
||||
bookId={id}
|
||||
chapterId={item.id}
|
||||
title={item.title}
|
||||
isAuthor={isAuthor}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<Stack justifyContent="center" alignItems="center" pt={8}>
|
||||
<Text>No chapters here yet!</Text>
|
||||
{isAuthor && (
|
||||
<Link to={`/work/book/${id}/chapter/new`}>
|
||||
<Button variant="outline" size="sm">
|
||||
Create new chapter
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
{data.bookChapterMany.total > chaptersListPageSize && (
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<Pagination
|
||||
total={data.bookChapterMany.total}
|
||||
page={chaptersListPage}
|
||||
setPage={setChaptersListPage}
|
||||
pageSize={chaptersListPageSize}
|
||||
setPageSize={setChaptersListPageSize}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Stack direction="row">
|
||||
<>
|
||||
{isAuthor && data.bookChapterMany.items.length !== 0 && (
|
||||
<Box>
|
||||
<Link to={`/work/book/${id}/chapter/new`}>
|
||||
<Button size="sm">Create new chapter</Button>
|
||||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
</Stack>
|
||||
</>
|
||||
) : (
|
||||
<PaginatedListSkeleton disableSearch />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export const bookChapterListQuery = gql`
|
||||
query BookChapterList($input: BookChapterManyInput!) {
|
||||
bookChapterMany(input: $input) {
|
||||
items {
|
||||
...BookChapter
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
${bookChapterFragment}
|
||||
`;
|
||||
|
||||
const bookRatingQuery = gql`
|
||||
query BookOverviewRating($id: ID!) {
|
||||
bookOne(id: $id) {
|
||||
id
|
||||
ratingTotal
|
||||
personalRating
|
||||
chaptersTotal
|
||||
author {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const bookRatingMutation = gql`
|
||||
mutation BookOverviewRatingUpdate($input: RatingSetInput!) {
|
||||
ratingSet(input: $input)
|
||||
}
|
||||
`;
|
||||
119
app/client/pages/work/book/$bookId/overview/reviews.tsx
Normal file
119
app/client/pages/work/book/$bookId/overview/reviews.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { gql } from '@apollo/client';
|
||||
import { BookOverviewTabs } from '@app/client/components/BookOverviewTabs';
|
||||
import { Pagination } from '@app/client/components/Pagination';
|
||||
import { reviewFragment } from '@app/client/fragments';
|
||||
import { useIdentity } from '@app/client/hooks/identity';
|
||||
import { usePaginatedQuery } from '@app/client/hooks/usePaginatedQuery';
|
||||
import { useToastMutation } from '@app/client/hooks/useToastMutation';
|
||||
import { Skeleton, Stack, Text } from '@chakra-ui/react';
|
||||
import { BookReviewCreateMutation, BookReviewsQuery } from '@generated/types';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { useState } from 'react';
|
||||
import { bookQuery } from '../overview';
|
||||
import { ReviewInput } from '@app/client/components/ReviewInput';
|
||||
import { ReviewCard } from '@app/client/components/ReviewCard';
|
||||
|
||||
export const Route = createFileRoute('/work/book/$bookId/overview/reviews')({
|
||||
component: RouteComponent
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { bookId: id } = Route.useParams();
|
||||
const identity = useIdentity();
|
||||
|
||||
const [reviewsPage, setChaptersListPage] = useState<number>(1);
|
||||
const [reviewsPageSize, setChaptersListPageSize] = useState<number>(10);
|
||||
const startIndex = (reviewsPage - 1) * reviewsPageSize;
|
||||
|
||||
const { data } = usePaginatedQuery<BookReviewsQuery>({
|
||||
query: bookReviewsQuery,
|
||||
input: {
|
||||
bookId: id,
|
||||
filter: {
|
||||
offset: startIndex,
|
||||
size: reviewsPageSize
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { update: reviewCreate } = useToastMutation<BookReviewCreateMutation>({
|
||||
mutation: bookReviewCreateMutation,
|
||||
successMsg: 'Review posted!',
|
||||
refetchQueries: [bookReviewsQuery, bookQuery]
|
||||
});
|
||||
|
||||
if (data == null) return <Skeleton />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<BookOverviewTabs bookId={id} currentTab="reviews" />
|
||||
|
||||
{identity != null && reviewsPage === 1 && (
|
||||
<ReviewInput
|
||||
username={identity?.data.user.username}
|
||||
onReviewSubmit={async text => {
|
||||
await reviewCreate({
|
||||
variables: {
|
||||
input: {
|
||||
bookId: id,
|
||||
text
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{data.reviewMany.items.length === 0 ? (
|
||||
<Stack justifyContent="center" alignItems="center" pt={8}>
|
||||
<Text>No reviews here yet!</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
data.reviewMany.items.map(item => (
|
||||
<ReviewCard
|
||||
key={`bookReview#${item.id}`}
|
||||
review={{
|
||||
id: item.id,
|
||||
username: item.author.username,
|
||||
text: item.text,
|
||||
createdAt: item.createdAt
|
||||
}}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
{data.reviewMany.total > reviewsPageSize && (
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<Pagination
|
||||
total={data.reviewMany.total}
|
||||
page={reviewsPage}
|
||||
setPage={setChaptersListPage}
|
||||
pageSize={reviewsPageSize}
|
||||
setPageSize={setChaptersListPageSize}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const bookReviewsQuery = gql`
|
||||
query BookReviews($input: ReviewManyInput!) {
|
||||
reviewMany(input: $input) {
|
||||
items {
|
||||
...Review
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
${reviewFragment}
|
||||
`;
|
||||
|
||||
const bookReviewCreateMutation = gql`
|
||||
mutation BookReviewCreate($input: ReviewCreateInput!) {
|
||||
reviewCreate(input: $input) {
|
||||
...Review
|
||||
}
|
||||
}
|
||||
${reviewFragment}
|
||||
`;
|
||||
Loading…
Add table
Add a link
Reference in a new issue