import { useEffect, useState } from 'react'; import { api, get } from '../lib/api'; import { PageHeader } from '../components/PageHeader'; import { useToast } from '../components/Toast'; import { Plus, RefreshCw } from '../components/Icons'; interface SignupCode { id: number; code: string; descriptor: string; createdAt: string; expiresAt: string | null; revoked: boolean; redeemedAt: string | null; redeemedByPlayerId: number | null; redeemedByUsername: string | null; status: 'active' | 'redeemed' | 'revoked' | 'expired'; } const STATUS_BADGE: Record = { active: 'badge-online', redeemed: 'badge-admin', revoked: 'badge-banned', expired: 'badge-neutral', }; export function SignupCodes({ embedded }: { embedded?: boolean } = {}) { const [codes, setCodes] = useState(null); const [err, setErr] = useState(null); const [descriptor, setDescriptor] = useState(''); const [expiresAt, setExpiresAt] = useState(''); const [busy, setBusy] = useState(false); const toast = useToast(); const load = async () => { try { setCodes(await get('/signup-codes')); setErr(null); } catch (e) { setErr((e as Error).message); } }; useEffect(() => { void load(); }, []); const generate = async () => { setBusy(true); try { const created = await api<{ code: string }>('/signup-codes', { method: 'POST', body: { Descriptor: descriptor.trim(), ExpiresAt: expiresAt ? new Date(expiresAt).toISOString() : null, }, }); setDescriptor(''); setExpiresAt(''); toast.push(`Code ${created.code} created.`, 'success'); await load(); } catch (e) { toast.push((e as Error).message, 'error'); } finally { setBusy(false); } }; const revoke = async (id: number, code: string) => { if (!confirm(`Revoke code ${code}? It can no longer be redeemed.`)) return; try { await api(`/signup-codes/${id}/revoke`, { method: 'POST' }); toast.push('Code revoked.', 'success'); await load(); } catch (e) { toast.push((e as Error).message, 'error'); } }; const copy = async (code: string) => { try { await navigator.clipboard.writeText(code); toast.push('Code copied.', 'success'); } catch { toast.push('Copy failed — select it manually.', 'error'); } }; const refreshBtn = ( ); return (
{embedded ? (
{refreshBtn}
) : ( )} {err && (
{err}
)}

Generate a code

{!codes && !err &&
Loading…
} {codes && codes.length === 0 && (
No codes yet — generate one above.
)} {codes && codes.length > 0 && ( {codes.map((c) => ( ))}
Code Descriptor Status Redeemed by Expires
{c.code} {c.descriptor || —} {c.status} {c.redeemedByUsername ? {c.redeemedByUsername} ({c.redeemedByPlayerId}) : —} {c.expiresAt ? new Date(c.expiresAt).toLocaleString() : never} {c.status === 'active' && ( )}
)}
); }