From b81974476e6a6a7e366e111cc452013312cd4f4f Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Tue, 22 Sep 2026 21:41:48 -0400 Subject: [PATCH] [www] token drop functionality --- apps/www/src/client/App.tsx | 82 +++++++++++++++++++++++ apps/www/src/staff.ts | 73 +++++++++++++++++++- apps/www/src/test/integration/api.test.ts | 71 +++++++++++++++++++- apps/www/src/www.app.ts | 3 + packages/domain/src/presence-db.ts | 19 ++++++ 5 files changed, 246 insertions(+), 2 deletions(-) diff --git a/apps/www/src/client/App.tsx b/apps/www/src/client/App.tsx index ae4b65f..7842aff 100644 --- a/apps/www/src/client/App.tsx +++ b/apps/www/src/client/App.tsx @@ -3068,6 +3068,10 @@ function Dashboard({ { id: 'coach', label: 'Coach message', render: () => }, ] : []), + // Narrower than the two above: the drop mints tokens, and www's route is developer-only. + ...(isDeveloper() + ? [{ id: 'tokens', label: 'Token drop', render: () => }] + : []), ] const [active, setActive] = useState(sections[0].id) const current = sections.find((s) => s.id === active) ?? sections[0] @@ -3355,6 +3359,84 @@ function MaintenanceForm() { ) } +/** + * The most a token drop carries per player — a mirror of www's `MAX_TOKEN_DROP`, which is + * the one that decides. Not imported: the staff module pulls hono and the JWT code into the + * page bundle for one number. + */ +const MAX_TOKEN_DROP = 1_000 + +/** + * Developer-only: give every player online right now the same number of tokens, in a gift + * box carrying the operator's message — the server-wide cousin of the room page's staff gift. + * + * The audience is read by the server when the button is pressed, so it is whoever is on at + * that moment, lobby included; the button says as much. The cap is the server's + * (`MAX_TOKEN_DROP`, 1,000 each) and is repeated on the input so a typo is caught before the + * round trip — the server is still the one that refuses. + */ +function TokenDropForm() { + const [message, setMessage] = useState('') + const [tokens, setTokens] = useState('') + const { pending, error, done, run } = useAction() + + return ( +
+

Token drop

+

+ Give everyone online right now the same number of tokens. Each player gets a gift box with + your message on it; a player who isn't signed in when you press the button gets + nothing. Up to {MAX_TOKEN_DROP.toLocaleString()} tokens each. +

+
{ + e.preventDefault() + void run(async () => { + const amount = Number(tokens) + const res = await call<{ paid: number[] }>('/api/staff/online/gift-tokens', { + authed: true, + json: { amount, message: message.trim() }, + }) + setTokens('') + setMessage('') + const n = res.paid.length + return `Sent ${amount.toLocaleString()} tokens to ${n} online player${n === 1 ? '' : 's'}.` + }) + }} + > +