feat(manga): add import file button to manga page
This commit is contained in:
parent
8b27e56758
commit
5ddb122b88
@ -150,6 +150,7 @@ export interface DefaultResponseDTOMangaDTO {
|
||||
|
||||
export interface MangaDTO {
|
||||
id: number;
|
||||
malId: number;
|
||||
/** @minLength 1 */
|
||||
title: string;
|
||||
coverImageKey?: string;
|
||||
|
||||
149
src/features/manga/ManualImportDialog.tsx
Normal file
149
src/features/manga/ManualImportDialog.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { FileUp } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useImportMultipleFiles } from "@/api/generated/manga-import/manga-import.ts";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
|
||||
interface ManualImportDialogProps {
|
||||
malId: number;
|
||||
mangaTitle: string;
|
||||
dialogOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
queryKey: any;
|
||||
}
|
||||
|
||||
export const ManualImportDialog = ({
|
||||
dialogOpen,
|
||||
onOpenChange,
|
||||
malId,
|
||||
mangaTitle,
|
||||
queryKey,
|
||||
}: ManualImportDialogProps) => {
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
const [files, setFiles] = useState<File[] | null>(null);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending } = useImportMultipleFiles({
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
setFiles(null);
|
||||
onOpenChange(false);
|
||||
toast.success("Files imported successfully!");
|
||||
queryClient.invalidateQueries(queryKey);
|
||||
},
|
||||
onError: () => toast.error("Failed to import files."),
|
||||
},
|
||||
});
|
||||
|
||||
const handleFileImport = () => {
|
||||
if (!files) {
|
||||
alert("Please select one or more files to upload");
|
||||
return;
|
||||
}
|
||||
|
||||
mutate({ data: { malId: malId.toString(), files } });
|
||||
};
|
||||
|
||||
const handleDrag = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.type === "dragenter" || e.type === "dragover") {
|
||||
setDragActive(true);
|
||||
} else if (e.type === "dragleave") {
|
||||
setDragActive(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragActive(false);
|
||||
|
||||
if (e.dataTransfer.files) {
|
||||
setFiles(Array.from(e.dataTransfer.files));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
setFiles(Array.from(e.target.files));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={dialogOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Upload Files</DialogTitle>
|
||||
<DialogDescription>
|
||||
Upload one or more files to {mangaTitle}.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<label className="text-sm font-medium">Upload Files</label>
|
||||
<div
|
||||
onDragEnter={handleDrag}
|
||||
onDragLeave={handleDrag}
|
||||
onDragOver={handleDrag}
|
||||
onDrop={handleDrop}
|
||||
className={`mt-2 rounded-lg border-2 border-dashed p-6 text-center transition-colors ${
|
||||
dragActive
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-muted-foreground/25"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
id="file-input"
|
||||
onChange={handleFileSelect}
|
||||
className="hidden"
|
||||
multiple
|
||||
accept=".cbz"
|
||||
/>
|
||||
<label htmlFor="file-input" className="cursor-pointer">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<FileUp className="h-8 w-8 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
{files
|
||||
? files.map((file) => file.name).join(", ")
|
||||
: "Drag and drop your files here"}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
or click to select (.CBZ, .CBR)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
disabled={isPending}
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setFiles(null);
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={isPending} onClick={handleFileImport}>
|
||||
Import
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@ -7,6 +7,7 @@ import {
|
||||
Calendar,
|
||||
ChevronDown,
|
||||
Database,
|
||||
FileUp,
|
||||
Heart,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
@ -35,6 +36,7 @@ import {
|
||||
} from "@/components/ui/collapsible.tsx";
|
||||
import { useAuth } from "@/contexts/AuthContext.tsx";
|
||||
import { MangaChapter } from "@/features/manga/MangaChapter.tsx";
|
||||
import { ManualImportDialog } from "@/features/manga/ManualImportDialog.tsx";
|
||||
import { formatToTwoDigitsDateRange } from "@/utils/dateFormatter.ts";
|
||||
|
||||
const Manga = () => {
|
||||
@ -45,6 +47,8 @@ const Manga = () => {
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [isUploadFilesDialogOpen, setIsUploadFilesDialogOpen] = useState(false);
|
||||
|
||||
const { data: mangaData, queryKey } = useGetManga(mangaId);
|
||||
|
||||
const { mutate, isPending: fetchPending } = useFetchMangaChapters({
|
||||
@ -165,7 +169,19 @@ const Manga = () => {
|
||||
</Button>
|
||||
<h1 className="text-xl font-bold text-foreground">MangaMochi</h1>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
<div className="flex items-center gap-6">
|
||||
{isAuthenticated && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setIsUploadFilesDialogOpen(true)}
|
||||
className="gap-2"
|
||||
>
|
||||
<FileUp className="mr-2 h-4 w-4" />
|
||||
Upload Files
|
||||
</Button>
|
||||
)}
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@ -425,6 +441,14 @@ const Manga = () => {
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<ManualImportDialog
|
||||
malId={mangaData?.data?.malId ?? -1}
|
||||
mangaTitle={mangaData?.data?.title ?? ""}
|
||||
dialogOpen={isUploadFilesDialogOpen}
|
||||
onOpenChange={setIsUploadFilesDialogOpen}
|
||||
queryKey={queryKey}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user