/** * Component: BlocklistTable * Documentation: documentation/admin-features/release-blocklist.md * * Desktop = sortable table, mobile = stacked cards. Sortable columns clickable * with explicit affordance (cursor + sort icon) — per zach.md UX rule on * intentional affordances. */ 'use client'; import { useBlocklistUrlState } from '../hooks/useBlocklistUrlState'; import { BlockedReleaseRow, SortField } from '../types'; import { BlocklistRow } from './BlocklistRow'; interface BlocklistTableProps { entries: BlockedReleaseRow[]; onUnblocked: (id: string) => void; onUnblockFailed: (entry: BlockedReleaseRow, error: string) => void; } interface SortableHeaderProps { field: SortField; label: string; className?: string; } function SortableHeader({ field, label, className = '' }: SortableHeaderProps) { const { filters, setFilters } = useBlocklistUrlState(); const isActive = filters.sortBy === field; const order = filters.sortOrder; const handleClick = () => { if (isActive) { setFilters({ sortOrder: order === 'asc' ? 'desc' : 'asc' }); } else { setFilters({ sortBy: field, sortOrder: 'desc' }); } }; return ( ); } function SortGlyph({ active, order }: { active: boolean; order: 'asc' | 'desc' }) { if (!active) { return ( ); } return order === 'asc' ? ( ) : ( ); } export function BlocklistTable({ entries, onUnblocked, onUnblockFailed }: BlocklistTableProps) { return ( <> {/* Mobile cards */}
{entries.map((entry) => ( ))}
{/* Desktop table */}
{entries.map((entry) => ( ))}
Source Associated request Indexer Actions
); }