import { useState, useCallback, useRef, useEffect } from "react"; import { Upload, Loader2, X, FileIcon, ClipboardPaste, ImageIcon } from "lucide-react"; interface EvidenceUploadProps { onUpload: (file: File) => Promise; isUploading: boolean; } export default function EvidenceUpload({ onUpload, isUploading }: EvidenceUploadProps) { const [isDragging, setIsDragging] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [pasteHint, setPasteHint] = useState(false); const fileInputRef = useRef(null); const containerRef = useRef(null); // ── Clipboard paste handler ───────────────────────────────────── const handlePaste = useCallback( async (e: ClipboardEvent) => { const items = e.clipboardData?.items; if (!items) return; for (const item of Array.from(items)) { if (item.type.startsWith("image/")) { e.preventDefault(); const blob = item.getAsFile(); if (!blob) continue; // Generate a timestamped filename for pasted screenshots const ext = item.type.split("/")[1] || "png"; const ts = new Date() .toISOString() .replace(/[:.]/g, "-") .slice(0, 19); const file = new File([blob], `screenshot-${ts}.${ext}`, { type: item.type, }); setSelectedFile(file); // Brief pulse animation to confirm paste was detected setPasteHint(true); setTimeout(() => setPasteHint(false), 1500); return; } } }, [], ); // Listen for paste on the whole document while this component is mounted useEffect(() => { document.addEventListener("paste", handlePaste); return () => document.removeEventListener("paste", handlePaste); }, [handlePaste]); // ── Drag & drop ───────────────────────────────────────────────── const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const file = e.dataTransfer.files[0]; if (file) setSelectedFile(file); }, []); // ── File picker ───────────────────────────────────────────────── const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) setSelectedFile(file); }; // ── Upload ─────────────────────────────────────────────────────── const handleUpload = async () => { if (selectedFile) { await onUpload(selectedFile); setSelectedFile(null); if (fileInputRef.current) fileInputRef.current.value = ""; } }; const clearSelection = () => { setSelectedFile(null); if (fileInputRef.current) fileInputRef.current.value = ""; }; const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + " B"; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; return (bytes / (1024 * 1024)).toFixed(1) + " MB"; }; const isImage = selectedFile?.type.startsWith("image/"); return (
{/* Drop zone */}
fileInputRef.current?.click()} className={`cursor-pointer rounded-lg border-2 border-dashed p-6 text-center transition-all ${ pasteHint ? "border-cyan-400 bg-cyan-500/15 scale-[1.01]" : isDragging ? "border-cyan-500 bg-cyan-500/10" : "border-gray-700 bg-gray-800/50 hover:border-gray-600 hover:bg-gray-800" }`} >

{pasteHint ? ( Screenshot detected ✓ ) : isDragging ? ( "Drop file here" ) : ( <> Drag & drop, browse, or{" "} Ctrl+V to paste a screenshot )}

Paste from clipboard · Screenshots, logs, pcap… (max 50 MB)
{/* Selected file preview */} {selectedFile && (
{/* Image preview for pasted screenshots */} {isImage && (
Screenshot preview
preview
)}
{isImage ? ( ) : ( )}

{selectedFile.name}

{formatFileSize(selectedFile.size)} {isImage && ( screenshot )}

)}
); }