// One event's public page (EVENTS.md ยง API surface, Phase 14a). // // The storyline, its arc, what is live, what is next, what happened recently, // and a results table once an occurrence has published one. // // **`?run=` is read from the URL and passed straight through**, because that is // what an announcement's link carries. The page lives at the definition's slug โ€” // one stable address, so a link posted in Discord survives a retitle โ€” and the // occurrence has to be in the query string or a mail about last Friday's // invasion would open next Friday's. // // **The error is checked before the form.** Phase 13 found the inverse of this // on the admin editor: `if (loading || !form) return ` above the error // branch left a failed load spinning for ever with nothing on screen naming the // problem. Order matters, and the order is error first. import { useParams, useSearchParams, Link } from 'react-router-dom' import PublicLayout from '../../components/PublicLayout.jsx' import PageHeader from '../../components/PageHeader.jsx' import { Loading, ErrorState, EmptyState } from '../../components/PageState.jsx' import { useAsync } from '../../lib/useAsync.js' import { api } from '../../api/client.js' import { eventDateTime, statusWord } from '../../lib/eventCalendar.js' export default function EventPage() { const { slug } = useParams() const [params] = useSearchParams() const run = params.get('run') const { loading, error, data } = useAsync(() => api.publicEvent(slug, run), [slug, run]) if (error) { return (

Back to the calendar

) } if (loading || !data) { return (
) } const event = data.event const headline = event.current || event.next return (
{event.series && (

Part of {event.series.name}

)} {/* The one fact a visitor came for, before the storyline rather than after it: whether it is happening now, and if not, when it next is. */}
{event.live ? ( <>
Happening now
{/* The phase LABEL, and only while it is live. The plan behind the event is never published. */} {event.current.phase || 'Under way'}
) : event.next ? ( <>
Next
{eventDateTime(event.next.scheduledFor, event.next.timezone)}
) : (
Nothing scheduled at the moment.
)}
{event.body && (
)} {event.results && (

Results

{eventDateTime(event.results.scheduledFor, event.timezone)}

{event.results.participants.length === 0 ? ( Results were published with nobody recorded. ) : ( {event.results.participants.map((p, i) => ( {/* A module supplies a display name in `meta` or it does not; the member key is never published, so there is genuinely nothing else to render. */} ))}
# Who Score
{p.rank ?? 'โ€”'}{p.name || Unnamed} {p.score}
)}
)} {!headline && event.past.length === 0 && ( This event has not been scheduled yet. )}
) } function Occurrences({ title, list, slug, timezone, past = false }) { if (!list || list.length === 0) return null return (

{title}

) }