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 { 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(); load(); }} >
{rows.map((row) => ( ))} {rows.length === 0 && }
{"Username"} {"Reason"} {"Reserved by"} {"Reserved (UTC)"}
{`@${row.username}`} {row.reason || "-"} {row.actor || "-"} {formatUnix(row.created_at) || "-"} load()} />
{reserveOpen && ( setReserveOpen(false)} onDone={() => { setReserveOpen(false); load(); }} /> )}
); } function UnreserveButton({ username, onDone }: { username: string; onDone: () => void }) { const [busy, setBusy] = useState(false); return ( ); } function ReserveUsernameModal({ onClose, onDone }: { onClose: () => void; onDone: () => void }) { const [username, setUsername] = useState(""); const [reason, setReason] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const clean = username.trim().replace(/^@/, ""); const canSubmit = clean.length >= 5 && reason.trim().length > 0 && !busy; async function submit() { setBusy(true); setError(""); try { const result = await api.action("/api/actions/reserve-username", { username: clean, reason: reason.trim(), confirm: true, }); if (result.error) { setError(result.error); return; } onDone(); } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } return createPortal(
{"Usernames"}

{"Reserve a username"}