Merge branch 'fix/reserved-usernames-layout'

This commit is contained in:
Astra 2026-09-09 21:11:52 +01:00
commit 2f7ad81ae4
4 changed files with 75 additions and 40 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -23,7 +23,7 @@
})();
</script>
<script type="module" crossorigin src="/assets/index-BWYHyok0.js"></script>
<script type="module" crossorigin src="/assets/index-BYKuPGzl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CQKJMNpu.css">
</head>
<body>

View file

@ -1,5 +1,6 @@
import { AtSign, Loader2, Plus, RefreshCw, Search, Trash2 } from "lucide-react";
import { AtSign, 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";
@ -15,7 +16,7 @@ export function ReservedUsernamesPage() {
const [rows, setRows] = useState<ReservedUsernameRow[]>([]);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const [newName, setNewName] = useState("");
const [reserveOpen, setReserveOpen] = useState(false);
async function load() {
setBusy(true);
@ -36,16 +37,19 @@ export function ReservedUsernamesPage() {
void load();
}, []);
const cleanNew = newName.trim().replace(/^@/, "");
return (
<PageFrame
title={"Reserved usernames"}
eyebrow={"Usernames / Blocklist"}
actions={
<>
<button className="btn primary icon-text" type="button" onClick={() => setReserveOpen(true)}>
<Plus size={15} /> {"Reserve username"}
</button>
<button className="btn icon-text" type="button" onClick={() => load()} disabled={busy}>
<RefreshCw size={15} className={busy ? "spin" : ""} /> {"Refresh"}
</button>
</>
}
>
{error && <Alert>{error}</Alert>}
@ -54,28 +58,6 @@ export function ReservedUsernamesPage() {
</div>
<QueryPanel>
<div className="toolbar">
<label className="field-inline">
<span>{"Reserve"}</span>
<input
value={newName}
onChange={(event) => setNewName(event.target.value)}
placeholder={"support"}
/>
</label>
<ActionButton
disabled={cleanNew.length < 5}
label={"Reserve username"}
icon={<Plus size={15} />}
tone="neutral"
path="/api/actions/reserve-username"
payload={() => ({ username: cleanNew })}
onDone={() => {
setNewName("");
void load();
}}
/>
</div>
<form
className="toolbar"
onSubmit={(event) => {
@ -108,7 +90,7 @@ export function ReservedUsernamesPage() {
{rows.map((row) => (
<tr key={row.username}>
<td>
<strong>
<strong className="icon-text">
<AtSign size={13} />
{row.username}
</strong>
@ -133,6 +115,59 @@ export function ReservedUsernamesPage() {
</tbody>
</table>
</div>
{reserveOpen && (
<ReserveUsernameModal
onClose={() => setReserveOpen(false)}
onDone={() => {
setReserveOpen(false);
void load();
}}
/>
)}
</PageFrame>
);
}
function ReserveUsernameModal({ onClose, onDone }: { onClose: () => void; onDone: () => void }) {
const [username, setUsername] = useState("");
const clean = username.trim().replace(/^@/, "");
return createPortal(
<div className="modal-backdrop" role="presentation">
<section className="modal command-modal" role="dialog" aria-modal="true" aria-label={"Reserve a username"}>
<div className="modal-head">
<div>
<div className="eyebrow">{"Usernames"}</div>
<h2>{"Reserve a username"}</h2>
</div>
<button className="icon-btn" type="button" onClick={onClose} aria-label={"Close"}>
<X size={15} />
</button>
</div>
<div className="command-body">
<label className="duration-field">
<span>{"Username"}</span>
<input value={username} onChange={(event) => setUsername(event.target.value)} placeholder="support" />
</label>
<p className="bot-create-note">
{"No peer will be able to take this name until it is unreserved. Nothing is shown to users."}
</p>
</div>
<div className="modal-actions">
<button className="btn" type="button" onClick={onClose}>{"Close"}</button>
<ActionButton
disabled={clean.length < 5}
label={"Reserve username"}
icon={<Plus size={15} />}
tone="neutral"
path="/api/actions/reserve-username"
payload={() => ({ username: clean })}
onDone={onDone}
/>
</div>
</section>
</div>,
document.body
);
}