"use client"; import { useEffect, useState } from "react"; import Image from "next/image"; import { api } from "./StartGame"; import { PanoramaPreview } from "./PanoramaPreview"; import { reportCategories } from "@/shared/moderation"; import type { Appeal } from "./MapAppeal"; type ReviewMap = { id: string; name: string; description: string; state: string; update_state: string | null; source: string; panorama_count: number; map: string | null; image: string | null; report_strikes: number; banned_at: string | null; }; export function Moderation() { const [maps, setMaps] = useState([]), [reports, setReports] = useState< { id: string; dataset_id: string; reason: string; category: keyof typeof reportCategories; panorama_id: string | null; evidence_url: string | null; created_at: string; mapName: string; map: string | null; }[] >([]), [appeals, setAppeals] = useState([]), [error, setError] = useState(""), [busy, setBusy] = useState(false), [reasons, setReasons] = useState>({}); const [preview, setPreview] = useState(null), [loading, setLoading] = useState(true); const [reportPreview, setReportPreview] = useState<{ id: string; panorama: string | null; } | null>(null); async function load() { try { const d = await api<{ maps: ReviewMap[]; reports: typeof reports; appeals: Appeal[]; }>("moderation"); setMaps(d.maps); setReports(d.reports); setAppeals(d.appeals); setError(""); } catch (e) { setError((e as Error).message); } finally { setLoading(false); } } async function resolveReport(id: string, decision: "confirm" | "dismiss") { setBusy(true); setError(""); try { await api(`reports/${id}`, { decision, reason: reasons[id] }, "PATCH"); await load(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } async function reviewAppeal(id: string, decision: "approve" | "reject") { setBusy(true); setError(""); try { await api(`moderation/appeals/${id}`, { decision, reason: reasons[id] }); await load(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } useEffect(() => { load(); }, []); async function decide(datasetId: string, decision: string) { setBusy(true); setError(""); try { await api("moderation", { datasetId, decision, reason: reasons[datasetId] || undefined, }); await load(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } return ( <> {error && (

{error}

)} {loading &&

Loading review queue…

} {!loading && !maps.length && !error && (

The review queue is clear.

)} {maps.map((m) => (
{m.state} {m.update_state === "review" && ( Map update awaiting review )}

{m.name}

{m.report_strikes > 0 && (

{m.report_strikes}/3 confirmed reports {m.banned_at ? " · Banned; owner appeal required" : ""}

)}

{m.source} · {m.panorama_count} locations · {m.description}

{m.map && ( Map for moderator review )}