"use client"; import { useState, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { useToast } from "@/components/ui/use-toast"; import { Upload, FileVideo, X, CheckCircle2, Trash2 } from "lucide-react"; import { formatFileSize } from "@/lib/utils"; import { cn } from "@/lib/utils"; interface HighResUploadDialogProps { shotId: string; shotCode: string; currentFilename: string | null; open: boolean; onClose: () => void; onSuccess?: () => void; } export function HighResUploadDialog({ shotId, shotCode, currentFilename, open, onClose, onSuccess, }: HighResUploadDialogProps) { const [file, setFile] = useState(null); const [uploadProgress, setUploadProgress] = useState(0); const [uploadState, setUploadState] = useState<"idle" | "uploading" | "done" | "error">("idle"); const [isDragOver, setIsDragOver] = useState(false); const [isRemoving, setIsRemoving] = useState(false); const { toast } = useToast(); const handleFileSelect = (selected: File) => { if (!selected.type.startsWith("video/")) { toast({ title: "Please select a video file (.mov, .mp4, etc.)", variant: "destructive" }); return; } if (selected.size > 50 * 1024 * 1024 * 1024) { // 50 GB sanity cap toast({ title: "File too large (max 50 GB)", variant: "destructive" }); return; } setFile(selected); }; const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); const dropped = e.dataTransfer.files[0]; if (dropped) handleFileSelect(dropped); }, []); const handleUpload = async () => { if (!file) return; setUploadState("uploading"); setUploadProgress(0); try { await uploadViaXhr( `/api/shots/${shotId}/highres`, file, (p) => setUploadProgress(Math.round(p * 100)) ); setUploadProgress(100); setUploadState("done"); toast({ title: `High-res uploaded: ${file.name}` }); setTimeout(() => { onSuccess?.(); handleClose(); }, 800); } catch (err) { setUploadState("error"); toast({ title: "Upload failed", description: (err as Error).message, variant: "destructive", }); } }; const handleRemove = async () => { setIsRemoving(true); try { const res = await fetch(`/api/shots/${shotId}/highres`, { method: "DELETE" }); if (!res.ok) throw new Error("Remove failed"); toast({ title: "High-res file removed" }); onSuccess?.(); handleClose(); } catch (err) { toast({ title: "Failed to remove", description: (err as Error).message, variant: "destructive", }); } finally { setIsRemoving(false); } }; const resetState = () => { setFile(null); setUploadProgress(0); setUploadState("idle"); }; const handleClose = () => { if (uploadState === "uploading") return; resetState(); onClose(); }; return ( !o && handleClose()}> High Res File — {shotCode}
{/* Current file info */} {currentFilename && !file && (
{currentFilename}
)} {/* Drop zone */} {!file ? (
{ e.preventDefault(); setIsDragOver(true); }} onDragLeave={() => setIsDragOver(false)} onDrop={handleDrop} className={cn( "relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-10 transition-colors cursor-pointer", isDragOver ? "border-amber-500 bg-amber-500/10" : "border-zinc-700 hover:border-amber-500/50 hover:bg-zinc-800/50" )} onClick={() => document.getElementById("highres-file-input")?.click()} > e.target.files?.[0] && handleFileSelect(e.target.files[0])} />

{currentFilename ? "Drop replacement file here or click to browse" : "Drop high-res file here or click to browse"}

MOV, MP4 or any video format

) : (

{file.name}

{formatFileSize(file.size)}

{uploadState === "idle" && ( )} {uploadState === "done" && ( )}
)} {/* Upload progress */} {(uploadState === "uploading" || uploadState === "done") && (
{uploadState === "uploading" ? "Uploading…" : "Complete!"} {uploadProgress}%
)}
); } /** Upload a file via XHR so we get progress events. */ function uploadViaXhr( url: string, file: File, onProgress: (fraction: number) => void ): Promise { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append("file", file); const xhr = new XMLHttpRequest(); xhr.open("POST", url); xhr.upload.addEventListener("progress", (e) => { if (e.lengthComputable) onProgress(e.loaded / e.total); }); xhr.addEventListener("load", () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(); } else { try { const json = JSON.parse(xhr.responseText); reject(new Error(json.error ?? `HTTP ${xhr.status}`)); } catch { reject(new Error(`HTTP ${xhr.status}`)); } } }); xhr.addEventListener("error", () => reject(new Error("Network error"))); xhr.addEventListener("abort", () => reject(new Error("Upload cancelled"))); xhr.send(formData); }); }