156 lines
5.0 KiB
TypeScript
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.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>
|
|
);
|
|
};
|