import { useEffect, useState } from 'react' // A small stat-tile row for a "My Characters" page: total characters, how many // are online right now, and how many game accounts are linked. `scope` is the // shard api object (admin or player self-service). Renders nothing until an // account is linked, so the empty/link-prompt state below it stands alone. // // It fetches the same rosters GameAccounts loads; for a personal page that's at // most a couple of extra live round-trips, and keeps this presentational bit // decoupled from GameAccounts' per-account roster loading. function Tile({ value, label }) { return (
{value}
{label}
) } // Fold the settled roster results into totals. `complete` is false when any // account's roster failed (a partial result — shown as a dash rather than a // misleadingly low count). function summarizeRosters(rosters) { let chars = 0 let online = 0 let complete = true for (const r of rosters) { if (r.status !== 'fulfilled') { complete = false continue } const cs = r.value.chars || [] chars += cs.length online += cs.filter((c) => c.online).length } return { chars, online, complete } } export default function CharacterStats({ scope }) { const [stats, setStats] = useState(null) useEffect(() => { let cancelled = false ;(async () => { try { const accounts = await scope.accounts() const linked = accounts.length if (linked === 0) { if (!cancelled) setStats({ linked: 0 }) return } // Roster is a live round-trip and can be unavailable (503); tolerate a // partial result so a restarting shard doesn't blank the whole row. const rosters = await Promise.allSettled(accounts.map((a) => scope.roster(a.account))) if (!cancelled) setStats({ linked, ...summarizeRosters(rosters) }) } catch { if (!cancelled) setStats({ error: true }) } })() return () => { cancelled = true } }, [scope]) // Hidden until we know an account is linked (or while first loading). if (!stats || stats.error || stats.linked === 0) return null // Counts depend on live rosters; show a dash if none came back. const count = (n) => (stats.complete || stats.chars > 0 ? n : '—') return (
) }