Compare commits

..

No commits in common. "2f7ad81ae44f6ab36d321b91d7911b49415f896b" and "be8afdd7d9eff796127c617ca6e7e33601aba8df" have entirely different histories.

4 changed files with 40 additions and 75 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>
<script type="module" crossorigin src="/assets/index-BYKuPGzl.js"></script> <script type="module" crossorigin src="/assets/index-BWYHyok0.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CQKJMNpu.css"> <link rel="stylesheet" crossorigin href="/assets/index-CQKJMNpu.css">
</head> </head>
<body> <body>

View file

@ -1,6 +1,5 @@
import { AtSign, Loader2, Plus, RefreshCw, Search, Trash2, X } from "lucide-react"; import { AtSign, Loader2, Plus, RefreshCw, Search, Trash2 } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { api, errorMessage } from "../api"; import { api, errorMessage } from "../api";
import { ActionButton } from "../components/ActionButton"; import { ActionButton } from "../components/ActionButton";
import { Alert, EmptyRow, Metric, PageFrame, QueryPanel } from "../components/ui"; import { Alert, EmptyRow, Metric, PageFrame, QueryPanel } from "../components/ui";
@ -16,7 +15,7 @@ export function ReservedUsernamesPage() {
const [rows, setRows] = useState<ReservedUsernameRow[]>([]); const [rows, setRows] = useState<ReservedUsernameRow[]>([]);
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [reserveOpen, setReserveOpen] = useState(false); const [newName, setNewName] = useState("");
async function load() { async function load() {
setBusy(true); setBusy(true);
@ -37,19 +36,16 @@ export function ReservedUsernamesPage() {
void load(); void load();
}, []); }, []);
const cleanNew = newName.trim().replace(/^@/, "");
return ( return (
<PageFrame <PageFrame
title={"Reserved usernames"} title={"Reserved usernames"}
eyebrow={"Usernames / Blocklist"} eyebrow={"Usernames / Blocklist"}
actions={ 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}> <button className="btn icon-text" type="button" onClick={() => load()} disabled={busy}>
<RefreshCw size={15} className={busy ? "spin" : ""} /> {"Refresh"} <RefreshCw size={15} className={busy ? "spin" : ""} /> {"Refresh"}
</button> </button>
</>
} }
> >
{error && <Alert>{error}</Alert>} {error && <Alert>{error}</Alert>}
@ -58,6 +54,28 @@ export function ReservedUsernamesPage() {
</div> </div>
<QueryPanel> <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 <form
className="toolbar" className="toolbar"
onSubmit={(event) => { onSubmit={(event) => {
@ -90,7 +108,7 @@ export function ReservedUsernamesPage() {
{rows.map((row) => ( {rows.map((row) => (
<tr key={row.username}> <tr key={row.username}>
<td> <td>
<strong className="icon-text"> <strong>
<AtSign size={13} /> <AtSign size={13} />
{row.username} {row.username}
</strong> </strong>
@ -115,59 +133,6 @@ export function ReservedUsernamesPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
{reserveOpen && (
<ReserveUsernameModal
onClose={() => setReserveOpen(false)}
onDone={() => {
setReserveOpen(false);
void load();
}}
/>
)}
</PageFrame> </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
);
}