feat(provisioning): provisioning UI — signup, invites, accept page, unlink

Phase 6: the UI for the Phase 5 provisioning backend.

- CreateGameAccountForm: reusable game-account form (own username + password),
  mapping the sidecar errors (409/429/403/503) to friendly messages. Wired into
  GameAccounts (self-serve) — shown alongside the [link flow when the
  game_account_signup flag is on (exposed via public settings), so a registered
  player can create + link a game account from their portal.
- Admin Invites view (/admin/invites, admin-only): send an invite at a chosen
  access level, list invites with status, revoke pending ones. When email isn't
  configured the create response's accept link is surfaced to copy manually.
- Public accept page (/invite/:token): validates the invite, sets username +
  password (email + role pre-assigned), creates the account at that role and logs
  in; for a player invite it then offers the built-in "create game account" step
  before the portal. Honeypot-guarded like registration.
- Admin unlink wired into UserDetail via GameAccounts (per-account Unlink button,
  confirm + reconcile).
- Backend: expose gameAccountSignup availability in public settings.

Client build clean; server 193/193.

Refs .plans/protocol2-integration.md (Phase 6). Completes the Protocol 2.0/2.1 integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 16:06:01 -05:00
parent 91c206bf76
commit 2976d5982f
9 changed files with 442 additions and 11 deletions

View File

@@ -71,6 +71,9 @@ async function getPublic() {
// page show/hide the password form and SSO buttons.
const mode = REGISTRATION_MODES.includes(all[REGISTRATION_KEY]) ? all[REGISTRATION_KEY] : 'disabled'
out.registration = registrationFlags(mode)
// Whether the site offers game-account creation (the shard's own mode still has
// the final say when the call is made). Lets the portal show/hide the form.
out.gameAccountSignup = all[GAME_SIGNUP_KEY] === 'enabled'
return out
}