2025-12-26 16:08:36 +01:00
|
|
|
"use client";
|
|
|
|
|
|
2026-05-24 17:09:12 +02:00
|
|
|
import {DropZoneFile} from "@/components/common/dropzone-file";
|
2026-02-10 11:44:45 +01:00
|
|
|
import {useMutation, useQueryClient} from "@tanstack/react-query";
|
2026-02-13 11:24:53 +01:00
|
|
|
import {useRouter} from "next/navigation";
|
2025-12-26 16:08:36 +01:00
|
|
|
import {useState} from "react";
|
|
|
|
|
import {Loader2} from "lucide-react";
|
2026-05-24 17:09:12 +02:00
|
|
|
import {ButtonWithLoading} from "@/components/common/button-with-loading";
|
2025-12-26 16:08:36 +01:00
|
|
|
import {toast} from "sonner";
|
2026-05-24 17:09:12 +02:00
|
|
|
import {uploadBackupAction} from "@/features/database/import.action";
|
2025-12-26 16:08:36 +01:00
|
|
|
import {Card, CardContent} from "@/components/ui/card";
|
2026-04-02 19:47:05 +02:00
|
|
|
import {DatabaseWith} from "@/db/schema/07_database";
|
|
|
|
|
import {getFileHeadersBasedOnDbms} from "@/utils/common";
|
2025-12-26 16:08:36 +01:00
|
|
|
|
|
|
|
|
type UploadRetentionZoneProps = {
|
|
|
|
|
onSuccessAction?: () => void;
|
2026-04-02 19:47:05 +02:00
|
|
|
database: DatabaseWith;
|
2025-12-26 16:08:36 +01:00
|
|
|
};
|
|
|
|
|
|
2026-04-02 19:47:05 +02:00
|
|
|
export const UploadBackupZone = ({onSuccessAction, database}: UploadRetentionZoneProps) => {
|
2026-02-10 11:44:45 +01:00
|
|
|
const queryClient = useQueryClient();
|
2026-02-13 11:24:53 +01:00
|
|
|
const router = useRouter();
|
2025-12-26 16:08:36 +01:00
|
|
|
|
|
|
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
|
const [file, setFile] = useState<File | null>(null);
|
|
|
|
|
|
|
|
|
|
const mutationUpload = useMutation({
|
|
|
|
|
mutationFn: async (file: File) => {
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
setIsProcessing(true);
|
|
|
|
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("file", file);
|
2026-04-02 19:47:05 +02:00
|
|
|
formData.append("databaseId", database.id);
|
2025-12-26 16:08:36 +01:00
|
|
|
|
|
|
|
|
const result = await uploadBackupAction(formData)
|
|
|
|
|
|
|
|
|
|
const inner = result?.data;
|
|
|
|
|
if (inner?.success) {
|
|
|
|
|
toast.success(inner.actionSuccess?.message);
|
|
|
|
|
onSuccessAction?.()
|
2026-04-02 19:47:05 +02:00
|
|
|
queryClient.invalidateQueries({queryKey: ["database-data", database.id]});
|
2026-02-13 11:24:53 +01:00
|
|
|
router.refresh();
|
2025-12-26 16:08:36 +01:00
|
|
|
} else {
|
|
|
|
|
toast.error(inner?.actionError?.message);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error(err);
|
|
|
|
|
toast.error("An error occurred while upload in the backup");
|
|
|
|
|
} finally {
|
2026-04-02 19:47:05 +02:00
|
|
|
queryClient.invalidateQueries({queryKey: ["database-data", database.id]});
|
2025-12-26 16:08:36 +01:00
|
|
|
setIsProcessing(false);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-02 19:47:05 +02:00
|
|
|
const acceptDbImportFiles = getFileHeadersBasedOnDbms(database.dbms)
|
|
|
|
|
|
|
|
|
|
const fileKindDescription = Object.values(acceptDbImportFiles)
|
|
|
|
|
.flat()
|
|
|
|
|
.join(", ");
|
|
|
|
|
|
2025-12-26 16:08:36 +01:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{isProcessing ? (
|
|
|
|
|
<UploadLoader label="Uploading database backup…"/>
|
|
|
|
|
) : (
|
|
|
|
|
<DropZoneFile
|
2026-04-02 19:47:05 +02:00
|
|
|
accept={getFileHeadersBasedOnDbms(database.dbms)}
|
2026-02-13 14:28:55 +01:00
|
|
|
maxSize={2 * 1024 * 1024 * 1024}
|
2025-12-26 16:08:36 +01:00
|
|
|
maxFiles={1}
|
|
|
|
|
description="Import database backup"
|
2026-04-02 19:47:05 +02:00
|
|
|
fileKind={`Database file (${fileKindDescription})`}
|
2025-12-26 16:08:36 +01:00
|
|
|
dragMessage="Click or drag a database dump here"
|
|
|
|
|
onFileDropAction={(file: File) => setFile(file)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{(!isProcessing && file) && (
|
|
|
|
|
<div className="flex gap-4 justify-end">
|
|
|
|
|
<ButtonWithLoading
|
|
|
|
|
onClick={() => mutationUpload.mutateAsync(file)}
|
|
|
|
|
isPending={false}>
|
|
|
|
|
Upload
|
|
|
|
|
</ButtonWithLoading>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const UploadLoader = ({label = "Processing uploading..."}: { label?: string }) => {
|
|
|
|
|
return (
|
|
|
|
|
<Card className="w-full border">
|
|
|
|
|
<CardContent className="flex flex-col items-center justify-center gap-3 py-10 text-muted-foreground">
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<Loader2 className="w-8 h-8 animate-spin inset-0"/>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm font-medium">{label}</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
Do not close this window
|
|
|
|
|
</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
};
|