"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useRouter } from "next/navigation"; import { Trash2, RotateCcw, AlertTriangle, Clock } from "lucide-react"; import { api } from "@/lib/client/api"; import { fmtDate, timeAgo } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Badge, Dialog, DialogContent, Empty, Spinner } from "@/components/ui/primitives"; import { toast } from "@/components/ui/toast"; interface TrashedPage { id: string; title: string; icon: string | null; section: string; type: string; deletedAt: string; wordCount: number; updatedAt: string } const AUTO_PURGE_DAYS = 30; export function TrashView() { const qc = useQueryClient(); const r = useRouter(); const [purged, setPurged] = useState(null); const { data: items = [], isLoading, refetch } = useQuery({ queryKey: ["trash"], queryFn: () => api("/api/pages?view=trash&limit=100"), enabled: purged !== null, staleTime: 5_000 }); useEffect(() => { api("/api/trash", { method: "POST", json: { action: "purge-old", days: AUTO_PURGE_DAYS } }).then((res) => setPurged(res.purged ?? 0)).catch(() => setPurged(0)); }, []); const [confirm, setConfirm] = useState<{ kind: "one"; page: TrashedPage } | { kind: "all" } | null>(null); const [busy, setBusy] = useState(false); const refresh = () => { qc.invalidateQueries({ queryKey: ["trash"] }); qc.invalidateQueries({ queryKey: ["tree"] }); qc.invalidateQueries({ queryKey: ["dashboard"] }); }; async function restore(p: TrashedPage) { try { await api(`/api/pages/${p.id}?action=restore`, { method: "POST" }); toast("Restored", { description: p.title || "Untitled", tone: "success" }); refresh(); } catch (e: any) { toast("Restore failed", { description: e.message, tone: "danger" }); } } async function doConfirm() { if (!confirm) return; setBusy(true); try { if (confirm.kind === "one") { await api(`/api/pages/${confirm.page.id}?purge=1`, { method: "DELETE" }); toast("Deleted forever", { description: confirm.page.title || "Untitled" }); } else { const res = await api("/api/trash", { method: "DELETE" }); toast("Trash emptied", { description: `${res.purged} page${res.purged === 1 ? "" : "s"} deleted forever` }); } setConfirm(null); refresh(); } catch (e: any) { toast("Delete failed", { description: e.message, tone: "danger" }); } finally { setBusy(false); } } const daysLeft = (iso: string) => Math.max(0, AUTO_PURGE_DAYS - Math.floor((Date.now() - new Date(iso).getTime()) / 864e5)); return (

Trash

Pages are deleted forever after {AUTO_PURGE_DAYS} days in the trash.{purged ? ` ${purged} old item${purged === 1 ? "" : "s"} just purged.` : ""}

{isLoading || purged === null ?
: !items.length ? } title="Trash is empty" hint="Pages you move to the trash can be restored from here for 30 days." action={} /> : (
PageSectionDeletedWords
{items.map((p) => (
{p.icon ?? (p.type === "database" ? "๐Ÿ—‚" : p.type === "research" ? "๐Ÿ”ฌ" : "๐Ÿ“„")}{p.title || "Untitled"}
{p.section} {timeAgo(p.deletedAt)} ยท {daysLeft(p.deletedAt)} d left {p.wordCount} w
))}
)} !o && !busy && setConfirm(null)}>
This action is irreversible.
); }