import { Loader2, Plus, RefreshCw, Search, Trash2, X } from "lucide-react"; import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { api, errorMessage } from "../api"; import { ActionButton } from "../components/ActionButton"; import { Alert, EmptyRow, Metric, PageFrame, QueryPanel } from "../components/ui"; import { formatUnix } from "../lib/format"; import type { ReservedUsernameRow } from "../types"; // Reserved usernames are a plain operator blocklist: a name listed here cannot be // taken as an editable username by any peer and cannot be minted as a // collectible. No owner, no price, no "bought on Fragment" badge - that is the // collectible tab's job. export function ReservedUsernamesPage() { const [q, setQ] = useState(""); const [reserveOpen, setReserveOpen] = useState(false); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); async function load() { setLoading(true); setError(""); const params = new URLSearchParams({ limit: "200" }); if (q.trim()) params.set("q", q.trim().replace(/^@/, "")); try { const result = await api.reservedUsernames(params); setRows(result.reserved ?? []); } catch (err) { setError(errorMessage(err)); } finally { setLoading(false); } } useEffect(() => { void load(); }, []); return ( } > {error && {error}}
{ event.preventDefault(); void load(); }} >
{rows.map((row) => ( ))} {rows.length === 0 && }
{"Username"} {"Reason"} {"Reserved by"} {"Reserved (UTC)"}
{`@${row.username}`} {row.reason || "-"} {row.actor || "-"} {formatUnix(row.created_at) || "-"} } tone="danger" path="/api/actions/unreserve-username" payload={() => ({ username: row.username })} onDone={() => void load()} />
{reserveOpen && ( setReserveOpen(false)} onDone={() => { setReserveOpen(false); void load(); }} /> )}
); } // ReserveUsernameModal collects the name, then hands off to ActionButton for the // standard reason / dry-run / confirm flow - the same as every other admin // action. The name is read fresh from state on each ActionButton render. function ReserveUsernameModal({ onClose, onDone }: { onClose: () => void; onDone: () => void }) { const [username, setUsername] = useState(""); const clean = username.trim().replace(/^@/, ""); return createPortal(
{"Usernames"}

{"Reserve a username"}

{`No peer will be able to take @${clean || "…"} until it is unreserved. Nothing is shown to users.`}

} tone="neutral" path="/api/actions/reserve-username" payload={() => ({ username: clean })} onDone={onDone} />
, document.body, ); }