"use client"; import { useCallback, useState } from "react"; import { Can } from "@/components/staff-context"; import { toast } from "sonner"; import { api, AdminError, type AdminReport, type ReportType } from "@/lib/api"; import { usePagedList } from "@/lib/use-paged-list"; import { ListShell, EmptyRow } from "@/components/list-shell"; import { Table, THead, TR, TH, TD } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Modal } from "@/components/ui/modal"; import { RichTextarea } from "@/components/ui/rich-textarea"; import { RichText } from "@/lib/rich-text"; import { useConfirm } from "@/components/confirm"; import { cn, fmtDate } from "@/lib/utils"; import { Clamp } from "@/components/ui/clamp"; const TYPES: { key: ReportType; label: string }[] = [ { key: "post", label: "Posts" }, { key: "comment", label: "Comments" }, { key: "user", label: "Accounts" }, ]; export default function ReportsPage() { const [type, setType] = useState("post"); const [q, setQ] = useState(""); const fetcher = useCallback( (limit: number, offset: number) => api.reports(type, q, limit, offset), [type, q], ); const list = usePagedList(fetcher, [type, q]); const confirm = useConfirm(); const [resolveTarget, setResolveTarget] = useState(null); async function run(action: Promise, successMsg: string) { try { await action; toast.success(successMsg); list.refresh(); } catch (err) { toast.error(err instanceof AdminError ? err.message : "Action failed."); } } // The moderation button when the target is still live. Destructive actions // confirm first. (When the target is already removed we show "Undo" instead.) function modButton(r: AdminReport) { if (r.type === "post") { return ( <> ); } if (r.type === "comment") { return ( ); } return ( ); } // Shown when the reported content is currently removed (deleted / banned) — // reverts the soft-deletion or ban. function undoButton(r: AdminReport) { return ( ); } const targetLabel = type === "user" ? "Reported account" : type === "comment" ? "Comment" : "Post"; return ( {TYPES.map((t) => ( ))} } > {list.items.length === 0 && !list.loading ? ( ) : ( list.items.map((r) => { const resolved = !!r.resolved_at; return ( ); }) )}
{targetLabel} Reason Reporter Reported Actions
{type === "user" ? ( {r.target_text ? `@${r.target_text}` : "—"} ) : ( <> {r.target_text || "—"} {r.target_author_handle ? ( {`by @${r.target_author_handle}`} ) : null} )} {r.target_removed ? (
{r.type === "user" ? "Suspended" : "Removed"}
) : null}
{r.category}
{r.reason ? ( {r.reason} ) : null}
{r.reporter_handle ? `@${r.reporter_handle}` : "—"} {fmtDate(r.created_at)} {resolved ? (
✓ Resolved {r.resolution_note ? (

) : null} {r.target_removed ? undoButton(r) : null}
) : (
{r.target_removed ? undoButton(r) : modButton(r)}
)}
setResolveTarget(null)} onDone={() => { setResolveTarget(null); list.refresh(); }} />
); } function ResolveModal({ report, onClose, onDone, }: { report: AdminReport | null; onClose: () => void; onDone: () => void; }) { const [note, setNote] = useState(""); const [busy, setBusy] = useState(false); async function submit() { if (!report || note.trim() === "") return; setBusy(true); try { await api.resolveReport(report.type, report.id, note.trim()); toast.success("Report resolved."); setNote(""); onDone(); } catch (err) { toast.error( err instanceof AdminError ? err.message : "Could not resolve.", ); } finally { setBusy(false); } } return (
); }