import { useCallback, useEffect, useState } from "react"; import { api } from "./api.js"; import { useToast } from "./useToast.js"; interface Invite { code: string; available: number; disabled: boolean; createdAt: string; uses: { usedBy: string; usedAt: string }[]; } export function InvitesPanel() { const [invites, setInvites] = useState([]); const [hideUsed, setHideUsed] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [toast, showToast] = useToast(); const refresh = useCallback(() => { api .invites() .then((r) => setInvites(r.codes)) .catch((e) => setError(e.message)); }, []); useEffect(refresh, [refresh]); const create = async () => { setBusy(true); setError(null); try { await api.inviteCreate(1); refresh(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } }; const disable = async (code: string) => { setBusy(true); setError(null); try { await api.inviteDisable(code); refresh(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } }; const copy = (code: string, e: React.MouseEvent) => { const at = { clientX: e.clientX, clientY: e.clientY }; navigator.clipboard .writeText(code) .then(() => showToast("invite code copied", at)) .catch(() => showToast("copy failed", at)); }; const isDead = (inv: Invite) => inv.disabled || inv.uses.length >= inv.available; const shown = hideUsed ? invites.filter((inv) => !isDead(inv)) : invites; return (

Invite Codes ({shown.length})

{error &&
{error}
} {shown.length === 0 && !error && (

{hideUsed ? "no unused invite codes" : "no invite codes yet"}

)} {shown.length > 0 && ( {shown.map((inv) => { const spent = inv.uses.length >= inv.available; return ( ); })}
Code Uses Created
{inv.code} {inv.uses.length}/{inv.available} {new Date(inv.createdAt).toLocaleDateString()} {inv.disabled ? " (disabled)" : ""}
{!inv.disabled && !spent && ( )}
)} {toast}
); }