'use client'; import { useEffect, useState, useMemo } from 'react'; import { getHistory, clearHistory, removeMultipleFromHistory, type UploadRecord } from '@/lib/storage'; import { useNotify } from '@/lib/notifications'; import ErrorBoundary from '@/components/ErrorBoundary'; import PortalLayout from '@/app/layout-portal'; import BatchBar from '@/components/BatchBar'; import { Trash2 } from 'lucide-react'; import { recordKey, parseRecordKey } from './helpers'; import { HistoryHeader, HistorySearch, HistorySkeleton, HistoryEmpty, HistoryTable, HistoryMobileCard, HistoryNoResults, HistoryResultsCount, } from './components'; /* ════════════════════════════ Page ════════════════════════════ */ export default function HistoryPage() { const [history, setHistory] = useState([]); const [search, setSearch] = useState(''); const [copied, setCopied] = useState(null); const [loading, setLoading] = useState(true); const [selected, setSelected] = useState>(new Set()); function load() { setHistory(getHistory()); setLoading(false); } useEffect(() => { load(); }, []); /* ── Filter ── */ const filtered = useMemo(() => { if (!search.trim()) return history; const q = search.toLowerCase(); return history.filter( (r) => r.name.toLowerCase().includes(q) || r.cid.toLowerCase().includes(q), ); }, [history, search]); /* ── Stats ── */ const stats = useMemo(() => { const totalUploads = history.length; const totalSize = history.reduce((sum, r) => sum + r.size, 0); const uniqueCids = new Set(history.map((r) => r.cid)).size; return { totalUploads, totalSize, uniqueCids }; }, [history]); /* ── Clear ── */ function handleClear() { if (!confirm('Are you sure you want to clear all upload history? This cannot be undone.')) return; clearHistory(); setHistory([]); } /* ── Clipboard ── */ async function copyToClipboard(text: string, key: string) { try { await navigator.clipboard.writeText(text); setCopied(key); setTimeout(() => setCopied(null), 2000); } catch (err) { console.error('[HistoryPage] Clipboard write failed:', err); } } /* ── Selection ── */ function toggleSelection(key: string) { setSelected((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); } function toggleAll() { if (selected.size === filtered.length) { setSelected(new Set()); } else { setSelected(new Set(filtered.map(recordKey))); } } /* ── Batch delete ── */ const { notify } = useNotify(); function handleBatchDelete() { const count = selected.size; if (!confirm(`Delete ${count} upload${count !== 1 ? 's' : ''} from history?`)) return; const keys = Array.from(selected).map(parseRecordKey); removeMultipleFromHistory(keys); setHistory((prev) => prev.filter((r) => !selected.has(recordKey(r)))); setSelected(new Set()); notify({ type: 'success', title: `Deleted ${count} upload${count !== 1 ? 's' : ''}` }); } /* ── Render ── */ return (
{history.length > 0 && ( )} {loading && } {!loading && history.length === 0 && } {filtered.length > 0 && ( <>
{filtered.map((rec) => ( ))}
{search && filtered.length < history.length && ( setSearch('')} /> )} )} {!loading && history.length > 0 && filtered.length === 0 && ( setSearch('')} /> )} setSelected(new Set())} actions={[ { key: 'delete', label: 'Delete selected', icon: , variant: 'danger' as const, onClick: handleBatchDelete, }, ]} />
); }