import { Link, useParams } from 'react-router-dom' import PublicLayout from '../../components/PublicLayout.jsx' import PageHeader from '../../components/PageHeader.jsx' import { Loading, ErrorState } from '../../components/PageState.jsx' import Slot from '../../modules/Slot.jsx' import { useAsync } from '../../lib/useAsync.js' import { api } from '../../api/client.js' import { emptyRosterReason, freshnessNote, rosterSummary } from '../../lib/teams.js' // The full roster (TEAMS.md §3.2). `shell="wide"` per §3.1 — this is the one // Team page with a table wide enough to want the room. // // **The link state is a first-class column, not an absence.** A roster where 37 // members show but only 21 carry a profile link looks broken until the page says // what the difference is; §3.2 exists because that gap is information (a // character with no site account behind it) and has to read as such. export default function TeamRoster() { const { slug } = useParams() const team = useAsync(() => api.team(slug), [slug]) const roster = useAsync(() => api.teamRoster(slug), [slug]) const members = roster.data?.members || [] const linked = members.filter((m) => m.linked).length const note = roster.data ? freshnessNote(roster.data) : null const emptyReason = roster.data ? emptyRosterReason(roster.data) : null return (

← Back to the Team

{(team.loading || roster.loading) && } {roster.error && } {!roster.loading && !roster.error && roster.data && ( <>

{rosterSummary({ members: members.length, linked })}

{note && (

{note.text}

)} {emptyReason &&

{emptyReason}

} {members.length > 0 && (
{/* Keyed by position, unavoidably: the row's stable identifier is its member key and that is exactly what is not published (§3.2). Two characters may share a display name. */} {members.map((member, i) => ( // eslint-disable-next-line react/no-array-index-key {/* The module's trailing cell. Nothing on bare core. §3.4 declares this slot's props as `{ memberKey, userId, displayName }`, and two of those cannot be supplied: §3.2 withholds the member key and the user id from every public roster response, so they are not in the payload this component was rendered from. Publishing them to reach the slot would put a game-internal identifier and a site account id on a public page for every visitor, module installed or not — the doc's two sections contradict each other and §3.2 is the one that is a security rule. The slot gets what core can honestly give it. */} ))}
Name Rank Status
{member.displayName} {member.isLeader && ( Leader )} {/* Muted text alone would read as a rendering glitch; the chip is what makes the gap in the header line legible. */} {!member.linked && ( not linked )} {member.rankLabel || '—'} {member.online ? 'Online' : 'Offline'}
)} )}
) }