"use client"; import { useRef, useState } from "react"; import { Video, Upload, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { updateShot } from "@/actions/shots"; import { useToast } from "@/components/ui/use-toast"; interface FootageViewerProps { shot: { id: string; shotCode: string; originalFootageUrl: string | null; originalFootageKey: string | null; }; canManage: boolean; onSaved?: () => void; } function uploadViaXhr( file: File, onProgress: (fraction: number) => void ): Promise<{ url: string; key: string }> { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append("file", file); const xhr = new XMLHttpRequest(); xhr.open("POST", "/api/upload/local"); xhr.upload.addEventListener("progress", (e) => { if (e.lengthComputable) onProgress(e.loaded / e.total); }); xhr.addEventListener("load", () => { if (xhr.status >= 200 && xhr.status < 300) { try { const json = JSON.parse(xhr.responseText); if (json.url) resolve({ url: json.url, key: json.key ?? "" }); else reject(new Error(json.error ?? "Upload failed")); } catch { reject(new Error("Invalid server response")); } } 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 aborted"))); xhr.send(formData); }); } export function FootageViewer({ shot, canManage, onSaved }: FootageViewerProps) { const { toast } = useToast(); const videoRef = useRef(null); const fileInputRef = useRef(null); const [footageFile, setFootageFile] = useState(null); const [uploading, setUploading] = useState(false); const [progress, setProgress] = useState(0); const [currentUrl, setCurrentUrl] = useState(shot.originalFootageUrl ?? null); const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setFootageFile(file); e.target.value = ""; }; const handleUpload = async () => { if (!footageFile) return; setUploading(true); setProgress(0); try { const { url, key } = await uploadViaXhr(footageFile, setProgress); await updateShot({ shotId: shot.id, originalFootageUrl: url, originalFootageKey: key || undefined, }); setCurrentUrl(url); setFootageFile(null); toast({ title: "Footage uploaded" }); onSaved?.(); } catch (e) { toast({ title: "Upload failed", description: e instanceof Error ? e.message : undefined, variant: "destructive", }); } finally { setUploading(false); setProgress(0); } }; const handleRemove = async () => { try { await updateShot({ shotId: shot.id, originalFootageUrl: null, originalFootageKey: null }); setCurrentUrl(null); setFootageFile(null); toast({ title: "Footage removed" }); onSaved?.(); } catch (e) { toast({ title: "Failed to remove footage", description: e instanceof Error ? e.message : undefined, variant: "destructive", }); } }; return (

{canManage && currentUrl && (
)}
{/* Video player */} {currentUrl && (
)} {/* Empty state */} {!currentUrl && !footageFile && (
canManage && fileInputRef.current?.click()} >
)} {/* Pending file — ready to upload */} {footageFile && !uploading && (
)} {/* Upload progress */} {uploading && (
Uploading footage… {Math.round(progress * 100)}%
)}
); }