import { useMemo } from 'react' import { useAsync } from '../lib/useAsync.js' import { useShardFeed } from '../lib/useShardFeed.js' import { bucketize } from '../data/regionBuckets.js' import { api } from '../api/client.js' // Compact live "Players Online" widget. Loads the presence.online aggregate once, // then keeps the total + region breakdown current from the presence.online SSE // kind. The raw byRegion map is rolled up into display buckets (see // data/regionBuckets.js). NOT a page — drop it into any panel/column. const PRESENCE_KINDS = new Set(['presence.online']) export default function PlayersOnline() { const { loading, error, data } = useAsync(() => api.shard.presence()) const { events } = useShardFeed({ filter: PRESENCE_KINDS, max: 4 }) // The freshest snapshot wins: the newest buffered presence.online event, else // the initial fetch. const snapshot = events[0] || data const { total, rows } = useMemo(() => { const count = Number(snapshot?.count) || 0 const { rows: bucketRows } = bucketize(snapshot?.byRegion) return { total: count, rows: bucketRows } }, [snapshot]) return (
Players online {loading ? '—' : total}
{error && (

Population is unavailable right now.

)} {!loading && !error && (
{rows.length === 0 ? (

{total > 0 ? 'Locations are settling…' : 'The realm is quiet.'}

) : ( rows.map((r) => (
{r.label} {/* tabular figures keep the right-aligned counts in a clean column */} {r.count}
)) )}
)}
) }