PURPOSE
Account center screen combining player identity (avatar, display name, badges, ID, email), a guest-to-claimed conversion CTA, dev convenience tooling (unlock all, reset all, +50K gems), an admin tools entry point, and sign-out. Renders inside V32Shell using the medical-UI design system (MedPanel cards, .med-btn variants, Cal Sans headlines, Space Grotesk body) per directive 2026-05-13T08:15Z (decisions/20260513-001). Ticket 77 medical-UI rollout, PR 5/5.
OWNS
- Local UI state via
useState:showClaim,editingName,nameInput,nameError,savingName,copied,unlocking,confirmUnlock,confirmReset. - Inline display-name edit flow (input + save/cancel buttons with validation: 1-24 characters).
- ID-copy interaction with 2-second “Copied!” feedback timer.
- Paired confirm-then-act flow for both
UNLOCK ALLandRESET ALL(each shows a “PERMANENTLY ALTER ACCOUNT?” prompt with YES/NO buttons before executing). - The render structure: player card, conditional claim CTA, unlock/reset row, +50K gems row, conditional admin tools button, sign-out.
READS FROM
usePlayerStore— selectsprofileandsignOutaction; profile drives identity rendering, guest/admin gating (profile.accountState === 'guest',profile.role === 'admin').react-router-domuseNavigate— used for/(post-sign-out) and/adminnavigation.navigator.clipboard— used byhandleCopyIdto writeprofile.id.
PUSHES TO
updateDisplayName(from../services/profileService) — persists the trimmed display name.unlockEverythingandresetEverything(from@starship-survivors/services/unlockService) — dev account mutations.invokeRpc(from../services/supabase) — calls thegrant_dev_currencySupabase RPC with{ p_currency: 'gems', p_amount: 50000 }; the returned wallet snapshot is pushed intouseWalletStoreviareplaceFromSnapshot.usePlayerStore.signOut— invoked byhandleSignOut, followed bynavigate('/').- Navigation to
/adminvianavigate('/admin')for the admin tools button. - Renders
ClaimAccountModalwhenshowClaimis true.
DOES NOT
- Does not own or render cosmetics, equipped ship, or inventory grids — the file notes a cosmetics grid is intentionally not present yet.
- Does not manage wallet balances directly beyond replacing them with the RPC snapshot.
- Does not perform routing guards or authentication checks; assumes
profileis available and shows a “Loading profile…” placeholder otherwise. - Does not throttle, batch, or rate-limit the unlock/reset/grant calls beyond the in-flight
unlockingflag and confirm-step gating. - Does not log errors to Sentry — failures from unlock/reset/grant are caught and
console.error-ed only. - Does not validate display-name content beyond length (1-24 characters after trim).
Signals
- Player card visual state switches between read mode (name + edit pencil) and edit mode (input + save/cancel) driven by
editingName. GUESTvsCLAIMEDbadge color: amber (var(--med-amber)) for guests, status-good for claimed.ADMINbadge appears only whenprofile.role === 'admin', invar(--med-status-bad)red.- Claim CTA button (
CLAIM ACCOUNT — SAVE YOUR PROGRESS) is rendered only for guest accounts. - Unlock button labels:
UNLOCK ALLidle,UNLOCKED!whileunlockingis true (button disabled). - Reset and unlock confirm panels share the same
⚠️ PERMANENTLY ALTER ACCOUNT?copy but color-code by destructiveness (amber for unlock, status-bad for reset). - The +50K gems button uses
var(--med-magenta)for its border and text. - Admin tools button is conditional on
isAdmin. - Sign-out is rendered last as a ghost variant button.
Entry points
- Default named export
ProfileScreen(function component) — mounted by the metagame router for the profile route insideV32Shell. ClaimAccountModalis mounted as a portal-like overlay sibling to the main flex column whenshowClaimis true.
Pattern notes
- Side-by-side dev tools row uses two equal-flex children (
flex: 1) for unlock/reset; each child swaps its content between an action button and aMedPanel variant="flat"confirmation panel based on the correspondingconfirm*state. - The +50K gems row is implemented as an array map over a single-element
as consttuple[{ currency: 'gems', label: '💎' }]— structured to accept additional currencies without restructuring. - Inline-styled buttons use design-token CSS variables (
--med-space-*,--med-radius-*,--med-amber,--med-magenta,--med-status-bad,--med-status-good,--med-border,--med-panel-recessed) rather than hardcoded values. - Imports
../screens/collection/collection.cssfor shared styling consumed elsewhere in collection screens. - Avatar is a literal rocket emoji
🚀at 28px inside a recessed 48x48 square — cosmetic art is not yet wired. - Error handling pattern is uniform:
try { await op() } catch (e) { console.error(...) }with no user-facing surface for failures other than the in-linenameErrorfor display-name updates. - Profile loading guard uses non-null assertion (
profile!.id) inside callbacks after the early-return on!profile, relying on the closure capture pattern.