"use client"; // The one dialog a curator writes to a reader through, whether the trigger is // a submission decision or a handled report. // // It was two nearly identical modals before the reports queue could reply at // all; making it one is what keeps the two from drifting on the things that // matter - the character cap, the "this is not public" promise, and the fact // that the recipient may not be reachable. import { useEffect, useRef, useState } from "react"; import { MESSAGE_MAX, type ReviewReason } from "@/lib/messages"; export interface MessageDialogProps { title: string; /// One or two sentences under the title explaining where the message goes. intro: string; /// Canned reasons. Picking one seeds the message box; omit for a plain /// free-text dialog. reasons?: ReviewReason[]; reasonLabel?: string; placeholder: string; confirmLabel: string; busy: boolean; /// False when there is nobody to deliver to - an account that has since /// been deleted. The action still runs; the message simply goes nowhere, /// and saying so up front beats letting someone write into a void. canDeliver?: boolean; onCancel: () => void; onConfirm: (reason: string | null, message: string) => void; } export function MessageDialog({ title, intro, reasons, reasonLabel = "Reason", placeholder, confirmLabel, busy, canDeliver = true, onCancel, onConfirm, }: MessageDialogProps) { const [reason, setReason] = useState(""); const [message, setMessage] = useState(""); const areaRef = useRef(null); // True once the curator has typed anything, after which picking a different // reason must not overwrite their words. const touched = useRef(false); useEffect(() => { function onKeyDown(event: KeyboardEvent) { if (event.key === "Escape" && !busy) onCancel(); } document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [busy, onCancel]); function pickReason(value: string) { setReason(value); const draft = reasons?.find((r) => r.value === value)?.draft ?? ""; // Seed the box only while it is still untouched or still holds a draft // this dialog put there. Anything the curator wrote themselves wins. if (!touched.current && draft) { setMessage(draft); areaRef.current?.focus(); } } return (

{title}

{intro}

{!canDeliver && (

This account no longer exists, so anything written here will not reach anyone. The action itself still goes through.

)}
{reasons && ( )}