trash-list.tsx
1 'use client' 2 3 import { useEffect, useState } from 'react' 4 import type { Agent } from '@/types' 5 import { useAppStore } from '@/stores/use-app-store' 6 import { api } from '@/lib/app/api-client' 7 import { ConfirmDialog } from '@/components/shared/confirm-dialog' 8 9 export function TrashList() { 10 const trashedAgents = useAppStore((s) => s.trashedAgents) 11 const loadTrashedAgents = useAppStore((s) => s.loadTrashedAgents) 12 const loadAgents = useAppStore((s) => s.loadAgents) 13 const [confirmPermanent, setConfirmPermanent] = useState<Agent | null>(null) 14 15 useEffect(() => { loadTrashedAgents() }, [loadTrashedAgents]) 16 17 const handleRestore = async (id: string) => { 18 await api('POST', '/agents/trash', { id }) 19 await Promise.all([loadTrashedAgents(), loadAgents()]) 20 } 21 22 const handlePermanentDelete = async (id: string) => { 23 await api('DELETE', '/agents/trash', { id }) 24 await loadTrashedAgents() 25 setConfirmPermanent(null) 26 } 27 28 const agents = Object.values(trashedAgents).sort( 29 (a, b) => (b.trashedAt ?? 0) - (a.trashedAt ?? 0), 30 ) 31 32 if (!agents.length) { 33 return ( 34 <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center"> 35 <div className="w-12 h-12 rounded-[14px] bg-white/[0.03] flex items-center justify-center"> 36 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/50"> 37 <polyline points="3 6 5 6 21 6" /> 38 <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /> 39 </svg> 40 </div> 41 <p className="text-[13px] text-text-3/50">Trash is empty</p> 42 </div> 43 ) 44 } 45 46 return ( 47 <div className="flex-1 overflow-y-auto"> 48 <div className="flex flex-col gap-1 px-2 pb-4 pt-2"> 49 {agents.map((agent) => ( 50 <div 51 key={agent.id} 52 className="py-3 px-4 rounded-[14px] border border-white/[0.04] bg-white/[0.02]" 53 > 54 <div className="flex items-center gap-2.5"> 55 <span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em] text-text-2/70"> 56 {agent.name} 57 </span> 58 </div> 59 <div className="text-[12px] text-text-3/50 mt-1 truncate">{agent.description}</div> 60 {agent.trashedAt && ( 61 <div className="text-[11px] text-text-3/40 mt-1"> 62 Trashed {formatRelative(agent.trashedAt)} 63 </div> 64 )} 65 <div className="flex items-center gap-2 mt-2.5"> 66 <button 67 onClick={() => handleRestore(agent.id)} 68 className="px-3 py-1.5 rounded-[8px] border border-white/[0.08] bg-transparent text-[12px] font-600 69 text-accent-bright cursor-pointer hover:bg-accent-soft transition-all" 70 style={{ fontFamily: 'inherit' }} 71 > 72 Restore 73 </button> 74 <button 75 onClick={() => setConfirmPermanent(agent)} 76 className="px-3 py-1.5 rounded-[8px] border border-white/[0.08] bg-transparent text-[12px] font-600 77 text-red-400 cursor-pointer hover:bg-red-400/10 transition-all" 78 style={{ fontFamily: 'inherit' }} 79 > 80 Delete Forever 81 </button> 82 </div> 83 </div> 84 ))} 85 </div> 86 87 <ConfirmDialog 88 open={!!confirmPermanent} 89 title="Permanently Delete" 90 message={`Permanently delete "${confirmPermanent?.name}"? This cannot be undone.`} 91 confirmLabel="Delete Forever" 92 danger 93 onConfirm={() => confirmPermanent && handlePermanentDelete(confirmPermanent.id)} 94 onCancel={() => setConfirmPermanent(null)} 95 /> 96 </div> 97 ) 98 } 99 100 function formatRelative(ts: number): string { 101 const diff = Date.now() - ts 102 const mins = Math.floor(diff / 60_000) 103 if (mins < 1) return 'just now' 104 if (mins < 60) return `${mins}m ago` 105 const hours = Math.floor(mins / 60) 106 if (hours < 24) return `${hours}h ago` 107 const days = Math.floor(hours / 24) 108 return `${days}d ago` 109 }