frontend/components/manga-chapter.tsx
2025-10-21 20:49:47 -03:00

156 lines
5.0 KiB
TypeScript

import { FC, useCallback, useState } from "react";
import {
useDownloadChapterArchive,
useFetchChapter,
useGetMangaChapters,
} from "@/api/mangamochi";
import { Check, Database, Download, Eye, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useQueryClient } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
interface MangaChapterProps {
mangaId: number;
mangaProviderId: number;
}
export const MangaChapter: FC<MangaChapterProps> = ({
mangaId,
mangaProviderId,
}) => {
const router = useRouter();
const { isPending, data, queryKey } = useGetMangaChapters(mangaProviderId);
const queryClient = useQueryClient();
const {
mutate: mutateDownloadChapterArchive,
isPending: isPendingDownloadChapter,
} = useDownloadChapterArchive({
mutation: {
onSuccess: (data, { chapterId }) => {
const url = window.URL.createObjectURL(data);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", chapterId + ".cbz");
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
},
},
});
const { mutate, isPending: isPendingFetchChapter } = useFetchChapter({
mutation: {
onSuccess: () => queryClient.invalidateQueries({ queryKey }),
onSettled: () => setFetchingId(null),
},
});
const [fetchingId, setFetchingId] = useState<number | null>(null);
const fetchChapter = useCallback(
(mangaChapterId: number) => {
setFetchingId(mangaChapterId);
mutate({ chapterId: mangaChapterId });
},
[mutate],
);
const handleReadChapter = (chapterNumber: number) => {
router.push(`/manga/${mangaId}/chapter/${chapterNumber}`);
};
return (
<div className="border-t border-border px-4 pb-4">
{isPending ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-primary" />
<span className="ml-2 text-sm text-muted-foreground">
Loading chapters...
</span>
</div>
) : data ? (
<div className="mt-4 space-y-2">
{data.map((chapter) => {
return (
<div
key={chapter.id}
className="flex items-center justify-between rounded-lg border border-border bg-background p-3"
>
<div className="flex items-center gap-3">
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${
chapter.isRead ? "bg-primary/20" : "bg-muted"
}`}
>
{chapter.isRead ? (
<Check className="h-4 w-4 text-primary" />
) : (
<span className="text-xs font-medium text-muted-foreground">
{/*{chapter}*/}
</span>
)}
</div>
<div>
<p className="text-sm font-medium text-foreground">
{chapter.title}
</p>
{chapter.downloaded && (
<p className="text-xs text-muted-foreground">
In database
</p>
)}
</div>
</div>
{chapter.downloaded ? (
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => handleReadChapter(chapter.id)}
className="gap-2"
>
<Eye className="h-4 w-4" />
Read
</Button>
<Button
size="sm"
variant="outline"
onClick={() =>
mutateDownloadChapterArchive({
chapterId: chapter.id,
params: { archiveFileType: "CBZ" },
})
}
className="gap-2"
>
<Download className="h-4 w-4" />
Download
</Button>
</div>
) : (
<Button
size="sm"
variant="outline"
onClick={() => fetchChapter(chapter.id)}
disabled={isPendingFetchChapter}
className="gap-2 cursor-pointer"
>
<Database className="h-4 w-4" />
{isPendingFetchChapter && fetchingId == chapter.id
? "Fetching..."
: "Fetch from Provider"}
</Button>
)}
</div>
);
})}
</div>
) : null}
</div>
);
};