import { createFileRoute, notFound } from "@tanstack/react-router"; import { z } from "zod"; import { actOnAbuse, getAbuseQueue } from "~/fns/abuse"; import { useAction } from "~/hooks/use-action"; import { ABUSE_CATEGORY_LABELS, ABUSE_TARGET_LABELS, HIDEABLE_KINDS } from "~/lib/abuse"; import type { AbuseAction, AbuseCategory, AbuseStatus } from "~/lib/abuse"; import { ModerateByUrl } from "~/components/admin/ModerateByUrl"; import { Badge } from "~/ui/Badge"; import { Button } from "~/ui/Button"; import { ErrorBanner } from "~/ui/Callout"; import { EmptyState } from "~/ui/EmptyState"; import { Heading } from "~/ui/Heading"; import { PageContainer } from "~/ui/PageContainer"; import { Panel } from "~/ui/Panel"; import { RelativeTime } from "~/ui/RelativeTime"; import { ExternalLink, TextLink } from "~/ui/TextLink"; const STATUSES: { value: AbuseStatus; label: string }[] = [ { value: "open", label: "Open" }, { value: "actioned", label: "Actioned" }, { value: "dismissed", label: "Dismissed" }, ]; export const Route = createFileRoute("/admin/abuse")({ head: () => ({ meta: [{ title: "Abuse queue · gitfor" }] }), validateSearch: z.object({ status: z.enum(["open", "actioned", "dismissed"]).optional() }), beforeLoad: ({ context }) => { if (!context.viewer?.isAdmin) throw notFound(); }, loaderDeps: ({ search }) => ({ status: search.status ?? "open" }), loader: ({ deps }) => getAbuseQueue({ data: { status: deps.status } }), component: AbuseQueuePage, }); type Report = Awaited>["reports"][number]; function siteState(report: Report) { if (!report.target_kind) return Unlinked URL; if (report.target_kind !== "site") { return ( <> {ABUSE_TARGET_LABELS[report.target_kind]} {report.target_hidden ? Hidden : null} ); } if (report.site_paused_at) return Paused; if (report.site_disabled_reason) return Disabled; return Live; } function ReportRow({ report }: { report: Report }) { const act = useAction(actOnAbuse); const run = (action: AbuseAction) => { const reason = action === "dismiss" ? null : window.prompt("Reason (recorded in the audit log)", ""); if (reason === null && action !== "dismiss") return; return act.run({ data: { id: report.id, action, reason: reason || null } }); }; const open = report.status === "open"; const hideable = report.target_kind !== null && HIDEABLE_KINDS.has(report.target_kind); const hideLabel = `Hide ${report.target_kind ?? ""}`; return (
  • {ABUSE_CATEGORY_LABELS[report.category as AbuseCategory] ?? report.category} {siteState(report)} {report.source} {report.target_url}

    {report.owner_name ? ( <> Owner{" "} {report.owner_name} ) : report.site_label ? ( "Unclaimed scratch site" ) : null} {report.reporter_email && <> · Reporter {report.reporter_email}} {report.resolution && <> · {report.resolution}}

    {report.details && (
              {report.details}
            
    )}
    {report.site_id && !report.site_disabled_reason && ( )} {report.site_paused_at && ( )} {open && report.site_paused_at && ( )} {hideable && !report.target_hidden && ( )} {hideable && Boolean(report.target_hidden) && ( )} {open && !report.site_id && ( )} {open && ( )}
  • ); } function AbuseQueuePage() { const { reports } = Route.useLoaderData(); const { status } = Route.useLoaderDeps(); return (
    Abuse queue
    Audit log Back to site admin
    {reports.length === 0 ? ( ) : (
      {reports.map((report) => ( ))}
    )}
    ); }