Phase 1: live dashboard — fixtures, scores, group tables, bracket
- Shared domain model (types, team metadata/flags/aliases, FIFA-tiebreak standings) - buildFixtures.ts: openfootball 2026 → normalized fixtures.json (104 matches, ISO-UTC kickoffs, pretty knockout placeholders) - Fastify live layer: seed load, football-data.org poller + SofaScore enhancement (feature-flagged, fallback), dynamic live-window polling, WS snapshot push, REST /api/snapshot, dev /api/dev/score injection - Client: tournament store (REST + WS + static-file fallback), MatchCard, TeamLabel, GroupTable, Live/Groups/Bracket pages, live connection indicator - Verified: WS pushes a dev-injected result; standings recompute live in-browser Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { teamFlag } from '@/lib/teams';
|
||||
import { cn } from '@/lib/cn';
|
||||
import type { TeamSlot } from '@/lib/types';
|
||||
|
||||
/** A team identity (flag + name) or, for an unresolved knockout slot, a muted
|
||||
* placeholder label ("Runner-up Group A"). */
|
||||
export function TeamLabel({
|
||||
slot,
|
||||
align = 'left',
|
||||
className,
|
||||
}: {
|
||||
slot: TeamSlot;
|
||||
align?: 'left' | 'right';
|
||||
className?: string;
|
||||
}) {
|
||||
const right = align === 'right';
|
||||
if (!slot.team) {
|
||||
return (
|
||||
<span
|
||||
className={cn('truncate text-sm italic text-faint', right && 'text-right', className)}
|
||||
title={slot.label}
|
||||
>
|
||||
{slot.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'flex min-w-0 items-center gap-2',
|
||||
right && 'flex-row-reverse',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className="text-lg leading-none">{teamFlag(slot.team)}</span>
|
||||
<span className="truncate font-medium text-ink">{slot.team}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user