"use client"; import { useState, useRef, useTransition, useEffect } from "react"; import { Upload, File, X, Loader2, CheckCircle2, AlertCircle } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; interface UploadDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } function formatSize(bytes: number): string { if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`; if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(0)} MB`; return `${(bytes / 1024).toFixed(0)} KB`; } type UploadStatus = "idle" | "uploading" | "processing" | "done" | "error"; export function UploadDialog({ open, onOpenChange }: UploadDialogProps) { const [files, setFiles] = useState([]); const [groupName, setGroupName] = useState(""); const [status, setStatus] = useState("idle"); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); const fileInputRef = useRef(null); const pollRef = useRef | null>(null); useEffect(() => { if (open) { setFiles([]); setGroupName(""); setStatus("idle"); setError(null); } return () => { if (pollRef.current) clearInterval(pollRef.current); }; }, [open]); function handleFileChange(e: React.ChangeEvent) { if (e.target.files) { setFiles(Array.from(e.target.files)); } } function removeFile(index: number) { setFiles((prev) => prev.filter((_, i) => i !== index)); } function handleUpload() { if (files.length === 0) return; startTransition(async () => { setStatus("uploading"); setError(null); try { const formData = new FormData(); for (const file of files) { formData.append("files", file); } if (groupName.trim()) { formData.append("groupName", groupName.trim()); } const res = await fetch("/api/uploads", { method: "POST", body: formData, }); const data = await res.json(); if (!res.ok) { setStatus("error"); setError(data.error ?? "Upload failed"); return; } setStatus("processing"); // Poll for completion pollRef.current = setInterval(async () => { try { const statusRes = await fetch(`/api/uploads/${data.uploadId}`); const statusData = await statusRes.json(); if (statusData.status === "COMPLETED") { setStatus("done"); toast.success(`${files.length} file(s) uploaded and indexed`); if (pollRef.current) clearInterval(pollRef.current); } else if (statusData.status === "FAILED") { setStatus("error"); setError(statusData.errorMessage ?? "Processing failed"); if (pollRef.current) clearInterval(pollRef.current); } } catch { // Keep polling } }, 3000); // Stop polling after 10 minutes setTimeout(() => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; setStatus((s) => s === "processing" ? "done" : s); } }, 600_000); } catch { setStatus("error"); setError("Network error"); } }); } return ( Upload Files Upload archive files to be processed and indexed. Multiple files will be automatically grouped. {status === "idle" && (
fileInputRef.current?.click()} >

Click to select files or drag & drop

ZIP, RAR, 7Z files up to 4GB each

{files.length > 0 && (
{files.map((file, i) => (
{file.name} {formatSize(file.size)}
))}
)} {files.length > 1 && (
setGroupName(e.target.value)} placeholder="Auto-generated from filenames" className="mt-1" />
)}
)} {(status === "uploading" || status === "processing") && (

{status === "uploading" ? "Uploading files..." : "Processing & uploading to Telegram..."}

{status === "uploading" ? "Sending files to server" : "Hashing, extracting metadata, uploading to destination channel"}

)} {status === "done" && (

Upload complete!

Files have been indexed and uploaded to Telegram.

)} {status === "error" && (

Upload failed

{error}

)} {status === "idle" && ( <> )} {(status === "done" || status === "error") && ( )}
); }