ClaimAccountModal

PURPOSE

Fullscreen overlay UI that lets a guest user upgrade their account by entering an email address and receiving a Supabase Auth magic link. Submits the email through playerStore.claimWithEmail, then switches to a “check your email” confirmation state. The actual account claim completes out-of-band when the user clicks the email link and the resulting auth state change triggers re-bootstrap in playerStore.

OWNS

  • Local form state: email (string), sending (boolean), sent (boolean), error (string or null).
  • The two-phase modal rendering: pre-submit form vs. post-submit confirmation.
  • Email validation gate (non-empty and contains @).
  • Submit lifecycle: clears prior error, sets sending, awaits claimWithEmail, transitions to sent, releases sending in a finally block.
  • Component-local error message derived from caught exceptions.

READS FROM

  • usePlayerStore selector s => s.claimWithEmail — the magic-link send action.
  • ClaimAccountModalProps.onClose — caller-provided dismiss callback.
  • Form input email value via controlled <input>.

PUSHES TO

  • playerStore via claimWithEmail(trimmed) — initiates the Supabase Auth OTP send.
  • onClose() — fired by the scrim click, the post-success “GOT IT” button, and the pre-submit “CANCEL” button.

DOES NOT

  • Does not complete the account claim itself; that work happens in playerStore after the magic link is clicked and the auth state changes.
  • Does not persist email anywhere outside React state.
  • Does not handle navigation, routing, or focus trapping beyond autoFocus on the input.
  • Does not render its own backdrop, panel chrome, or door styling — delegates to MedPanelModal and MedPanel variant="doors".
  • Does not retry on failure or rate-limit submissions beyond the sending disabled flag.
  • Does not validate email format beyond presence of @.

Signals

  • sent transitions false to true once claimWithEmail resolves; the rendered output switches branches.
  • sending gates the submit button label (SENDING... vs. SEND MAGIC LINK) and disables both input and submit while in flight.
  • error populated by either the validation guard (“Enter a valid email address”) or a caught exception (“Failed to send link” or the Error.message).

Entry points

  • Default export ClaimAccountModal({ onClose }) — invoked by the metagame shell when the user taps “Claim Account”.
  • handleSubmit(e: FormEvent) — bound to the <form onSubmit>; calls preventDefault, validates, then awaits the store action.

Pattern notes

  • Built on the medical-UI primitives MedPanelModal and MedPanel (variant doors), per the Tick 75 refactor away from V32 dark/gold inline styling.
  • Sibling-paired with WelcomeModal (T22 A2, t74) — same panel chrome, same two-phase pattern.
  • Uses MedPanelModal’s onScrimClick={onClose} for backdrop dismiss; no explicit ESC handling.
  • Inline style props remain for layout sizing (width: 'min(380px, 100%)', padding, margins); colors and typography come from med-* CSS classes and --med-* custom properties.
  • Two-button hierarchy in the form state: primary SEND MAGIC LINK (med-btn-primary) and ghost CANCEL (med-btn-ghost). The success state collapses to a single GOT IT primary button.
  • Error rendering uses med-text-critical with a slightly smaller fontSize: '13px'.
  • Type-only import of FormEvent follows the import { useState, type FormEvent } style.