import { Link } from 'react-router-dom' import PublicLayout from '../../components/PublicLayout.jsx' import PageHeader from '../../components/PageHeader.jsx' import { Loading, ErrorState } from '../../components/PageState.jsx' import { useAsync } from '../../lib/useAsync.js' import { useShardFeed } from '../../lib/useShardFeed.js' import { describe } from '../../lib/shardEvents.js' import { ago } from '../../lib/format.js' import { api } from '../../api/client.js' import PlayersOnline from '../../components/PlayersOnline.jsx' // ── Gold-supply sparkline ─────────────────────────────────────────────────── function Sparkline({ series }) { if (!series || series.length < 2) return null const w = 320 const h = 56 const golds = series.map((s) => Number(s.gold) || 0) const min = Math.min(...golds) const max = Math.max(...golds) const span = max - min || 1 const pts = series .map((s, i) => { const x = (i / (series.length - 1)) * w const y = h - ((Number(s.gold) || 0) - min) / span * h return `${x.toFixed(1)},${y.toFixed(1)}` }) .join(' ') return ( ) } // ── Stat tile (matches Status.jsx) ────────────────────────────────────────── function Stat({ value, label }) { return (
{value}
{label}
) } export default function Shard() { const { loading, error, data } = useAsync(() => Promise.all([ api.shard.status(), api.shard.idoc(), api.shard.economy(60), api.shard.online(), ]).then(([status, idoc, economy, online]) => ({ status, idoc, economy, online })), ) const { events, connected } = useShardFeed({ max: 30 }) const status = data?.status const online = status?.pluginConnected const gold = status?.economy?.gold return (
{loading && } {error && } {!loading && !error && data && ( <> {/* Connection banner */}
{online ? 'The shard is online' : 'The shard is offline'} {online ? 'The gate to Britannia stands open.' : status?.enabled ? 'The link to the game world is down — checking back automatically.' : 'Live shard data is not configured yet.'}
{/* Stat tiles */}
{/* Live players-online breakdown (total + region buckets) */}
{/* Staff online — linked staff accounts only, with location */}
Staff online
{(!data.online || data.online.length === 0) ? (

No staff are online right now.

) : (
{data.online.map((p) => (
{p.name || p.serial} {p.map || '—'}{p.x != null ? ` (${p.x}, ${p.y})` : ''}
))}
)}
{/* Economy sparkline */} {data.economy && data.economy.length > 1 && (
Gold supply over time
)}
{/* Latest IDOC */} ({ id: h.serial, text: `${h.name || 'A house'}${h.region ? ` — ${h.region}` : ''}`, when: h.updatedAt, }))} />
{/* Live ticker */}
Live feed
View all activity → {connected ? 'Live' : 'Offline'}
{events.length === 0 ? (

Waiting for something to happen in the world…

) : (
    {events.map((ev) => (
  • {describe(ev)} {ago(ev.t)}
  • ))}
)}
)}
) } function FeedList({ title, items, empty }) { return (
{title}
{items.length === 0 ? (

{empty}

) : ( )}
) }