import { useParams, Link } from 'react-router-dom' import api from '../../api.js' import { ErrorState, Loading, PageHeader, PublicLayout, Slot, useAsync } from '../../core.js' // One guild: its roster, and the place core puts the Team activity feed. // // **This page is the reason the extension-slot direction inverts** // (docs/website/TEAMS.md Part 3). Teams are a core platform primitive and this // module is what populates them — but core does not own the word "guild", so it // publishes no Team page of its own. The page is this module's; the activity feed // on it is core's, because only core can resolve whether the viewer is inside the // Team, and the public/members split on that feed is a security boundary. // // So the module declares `uo.guild.detail` (entry.jsx) and core fills it. On a // core that does not know about Teams the slot is simply never filled and this // page renders its roster alone, which is the same tolerance every other slot has. // // The roster comes from this module's OWN board — the same data it answers core's // Team provider from — rather than from core's Team API. That is deliberate: the // board is the authoritative copy here, and reading core's projection of our own // answer back would be a round trip through a staler copy of our own data. function rankOf(m) { // Absent rank means NOT KNOWN, never rank 0. The bridge omits it entirely for // staff, because ServUO reports GameMaster-and-above as Leader whatever their // real rank — emitting that verbatim would publish every staff member in a // guild as one of its leaders (docs/link/v4.md). if (m.rankName) return m.rankName return null } function MemberRow({ m }) { const rank = rankOf(m) const linked = m.webId != null || m.acct != null return ( {m.name || 'Unknown'} {m.rank === 4 && ( Leader )} {rank || '—'} {linked ? 'Linked' : '—'} ) } export default function Guild() { const { id } = useParams() const { loading, error, data } = useAsync(() => api.shard.guild(id), [id]) const roster = (data && data.roster) || [] return (

← All guilds

{loading && } {error && } {!loading && !error && data && ( <>

{data.members ?? roster.length} members {data.online != null && ` · ${data.online} online`} {data.alliance && ` · ${data.alliance}`}

{/* A third place for core, up here rather than below the roster: core puts this guild's notification control in it, and a control that acts on the page belongs beside the page's title and not after its content. Empty for a visitor with no membership, and on a core that fills nothing. */} {roster.length > 0 && (
{/* Keyed by serial: two characters can share a display name, which this shard's own world actually contains. */} {roster.map((m) => )}
Name Rank Account
)} {roster.length === 0 && (

No roster has been received for this guild yet.

)} {/* Core's Team activity feed lands here. Nothing renders on a core that does not fill it, or when there is nothing to show. The guild is named in OUR terms — core maps its own Team from these two. */} {/* And the Team forum, in its own place below the feed. Core resolves who may read it — membership and manual grants are core's rules — so this module renders the room and never its door policy. */} )}
) }