"use client"; // Flagging an entry for curator attention. The trigger is a small flag icon // button that sits with the vote and edit controls in the entry header; the // dialog collects a free-text explanation. Reports are private (they go to // the curators, not the page) and capped at three per day per person. import { useEffect, useState } from "react"; import Link from "next/link"; import { reportSubject } from "@/app/actions/report"; import type { Subject } from "@/lib/subject"; import { Icon } from "@/components/Icons"; import { useViewer } from "@/components/ViewerProvider"; /// Matches the vote buttons beside it: same border, radius, hover wash and /// EXACT height (their text-sm line + py-1.5 + border lands on 34px). export const CORNER_ICON_BUTTON = "inline-flex h-[34px] w-[34px] items-center justify-center rounded border border-[var(--hairline)] text-[var(--ink-secondary)] transition-colors hover:border-[var(--ink-muted)] hover:bg-[color-mix(in_srgb,var(--ink)_6%,transparent)] hover:text-[var(--ink)]"; const BODY_MAX = 1000; export function ReportEntryDialog({ subject, label = "Report an issue", }: { subject: Subject; /// The trigger's wording. A record's issues are usually a wrong value or a /// misattributed historical row, not a bad claim, so the record page says /// so instead of reusing the entry wording. label?: string; }) { const { signedIn, loaded } = useViewer(); // "entry" or "frontier", for the wording. The dialog is the same machinery // either way; only the noun and the examples of what to report differ. const noun = subject.kind === "frontier" ? "frontier" : "entry"; const [open, setOpen] = useState(false); const [body, setBody] = useState(""); const [sending, setSending] = useState(false); const [sent, setSent] = useState(false); const [error, setError] = useState(null); // Lock background scroll while the dialog is open. useEffect(() => { if (!open) return; const previous = document.body.style.overflow; document.body.style.overflow = "hidden"; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("keydown", onKey); return () => { document.body.style.overflow = previous; document.removeEventListener("keydown", onKey); }; }, [open]); function openDialog() { setBody(""); setError(null); setSent(false); setOpen(true); } async function send() { setSending(true); setError(null); const result = await reportSubject(subject, body); setSending(false); if (!result.ok) { setError(result.error); return; } setSent(true); } if (!loaded) { return ( ); } if (!signedIn) { return ( ); } return ( <> {open && (
setOpen(false)} aria-hidden />

Report this {noun}

{subject.kind === "frontier" ? "Tell the curators what is wrong - a value that is off, a step misattributed, a result missing from the history, a source that does not say what the row claims. Reports go straight to review and are never shown publicly. Up to three per day." : "Tell the curators what is wrong - a broken source, a misstated result, a claim that does not hold. Reports go straight to review and are never shown publicly. Up to three per day."}

{sent ? (

Thanks - the report is in. A curator will take a look.

) : ( <>