@burdenoff/microfe-atheletebridge 2026.720.1 → 2026.731.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/admin/AdminDashboardPage.js.map +1 -1
- package/dist/atheletebridge/admin/AdminRoot.js.map +1 -1
- package/dist/atheletebridge/components/AthleteSelect.js.map +1 -1
- package/dist/atheletebridge/components/Crest.js.map +1 -1
- package/dist/atheletebridge/components/CursorPager.js.map +1 -1
- package/dist/atheletebridge/components/DrillVideoLink.js.map +1 -1
- package/dist/atheletebridge/components/EmptyState.js.map +1 -1
- package/dist/atheletebridge/components/FilterSelect.js.map +1 -1
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -1
- package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
- package/dist/atheletebridge/components/PageLayout.js.map +1 -1
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
- package/dist/atheletebridge/components/RouteAnalytics.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -1
- package/dist/atheletebridge/components/SessionCard.js.map +1 -1
- package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -1
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/components/UserAvatar.js.map +1 -1
- package/dist/atheletebridge/components/UserSearchSelect.js.map +1 -1
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.d.ts +9 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.d.ts.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.js +118 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.js.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.d.ts +9 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.d.ts.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.js +88 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.js.map +1 -0
- package/dist/atheletebridge/components/domain.js.map +1 -1
- package/dist/atheletebridge/components/form.js.map +1 -1
- package/dist/atheletebridge/components/viz/BarChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/DonutChart.js +1 -1
- package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/LineChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -1
- package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -1
- package/dist/atheletebridge/components/viz/StatCard.js.map +1 -1
- package/dist/atheletebridge/components/viz/Timeline.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/format.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -1
- package/dist/atheletebridge/context/AthleteBridgeContext.js.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +18 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +71 -33
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/i18n.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +87 -75
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -1
- package/dist/atheletebridge/store/atheletebridgeStore.js.map +1 -1
- package/dist/atheletebridge/types/index.d.ts +27 -0
- package/dist/atheletebridge/types/index.d.ts.map +1 -1
- package/dist/atheletebridge/utils/cn.js.map +1 -1
- package/dist/atheletebridge/utils/datetime.js.map +1 -1
- package/dist/atheletebridge/utils/formatters.js.map +1 -1
- package/dist/atheletebridge/utils/identity.js.map +1 -1
- package/dist/atheletebridge/utils/navigation.js.map +1 -1
- package/dist/atheletebridge/utils/pageContext.js.map +1 -1
- package/dist/atheletebridge/utils/timeAgo.js.map +1 -1
- package/dist/atheletebridge/utils/url.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n *\n * The header is a crest-vs-crest Scoreboard hero; the record-result panel shows\n * each team's crest beside its score input. Semantic tokens only, responsive.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Trophy, Loader2, Flag, Minus } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Scoreboard } from '../../components/Scoreboard';\nimport { Crest } from '../../components/Crest';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\n\nexport function FixtureDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const fixture = useFixture(id);\n const recordResult = useRecordFixtureResult();\n\n const [homeScore, setHomeScore] = useState('');\n const [awayScore, setAwayScore] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n if (fixture.isLoading) {\n return (\n <PageLayout title=\"Fixture\">\n <LoadingState label=\"Loading fixture…\" />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title=\"Fixture\">\n <ErrorState\n title=\"Fixture not found\"\n message=\"This fixture does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? 'Home';\n const awayName = f.awayTeam?.name ?? (f.awayTeamId ? 'Away' : 'TBD');\n const canRecordResult =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const home = Number.parseInt(homeScore, 10);\n const away = Number.parseInt(awayScore, 10);\n if (Number.isNaN(home) || Number.isNaN(away)) {\n setError('Both scores are required and must be numbers.');\n return;\n }\n if (home < 0 || away < 0) {\n setError('Scores cannot be negative.');\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: { homeScore: home, awayScore: away } });\n setHomeScore('');\n setAwayScore('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record result.');\n }\n };\n\n return (\n <PageLayout\n title={`${homeName} vs ${awayName}`}\n description={`${FIXTURE_TYPE_LABELS[f.type]}${f.competition ? ` · ${f.competition}` : ''}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Crest-vs-crest hero scoreboard */}\n <Scoreboard fixture={f} variant=\"hero\" className=\"mb-8\" />\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Notes</h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {canRecordResult && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Flag className=\"h-5 w-5\" /> Record result\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-6\">\n <p className=\"mb-5 text-sm text-text-secondary\">\n Recording a final score marks this fixture <strong>Completed</strong> and recomputes\n the competition standings for both teams.\n </p>\n <form onSubmit={handleRecord}>\n <div className=\"flex flex-wrap items-end justify-center gap-4 sm:gap-6\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={homeName} sport={f.homeTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n </div>\n <FormField label=\"Home\" htmlFor=\"homeScore\" className=\"w-24\">\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n\n <Minus className=\"mb-3 h-5 w-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n\n <div className=\"flex items-end gap-3\">\n <FormField label=\"Away\" htmlFor=\"awayScore\" className=\"w-24\">\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={awayName} sport={f.awayTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-6 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n Record result\n </button>\n </div>\n </form>\n {error && <p className=\"mt-3 text-center text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" /> View {f.competition} standings\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n *\n * The header is a crest-vs-crest Scoreboard hero; the record-result panel shows\n * each team's crest beside its score input. Semantic tokens only, responsive.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Trophy, Loader2, Flag, Minus } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Scoreboard } from '../../components/Scoreboard';\nimport { Crest } from '../../components/Crest';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { AvailabilityRsvpControl } from '../../components/availability/AvailabilityRsvpControl';\nimport { AvailabilityRosterPanel } from '../../components/availability/AvailabilityRosterPanel';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\n\nexport function FixtureDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const fixture = useFixture(id);\n const recordResult = useRecordFixtureResult();\n\n const [homeScore, setHomeScore] = useState('');\n const [awayScore, setAwayScore] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n if (fixture.isLoading) {\n return (\n <PageLayout title=\"Fixture\">\n <LoadingState label=\"Loading fixture…\" />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title=\"Fixture\">\n <ErrorState\n title=\"Fixture not found\"\n message=\"This fixture does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? 'Home';\n const awayName = f.awayTeam?.name ?? (f.awayTeamId ? 'Away' : 'TBD');\n const canRecordResult =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const home = Number.parseInt(homeScore, 10);\n const away = Number.parseInt(awayScore, 10);\n if (Number.isNaN(home) || Number.isNaN(away)) {\n setError('Both scores are required and must be numbers.');\n return;\n }\n if (home < 0 || away < 0) {\n setError('Scores cannot be negative.');\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: { homeScore: home, awayScore: away } });\n setHomeScore('');\n setAwayScore('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record result.');\n }\n };\n\n return (\n <PageLayout\n title={`${homeName} vs ${awayName}`}\n description={`${FIXTURE_TYPE_LABELS[f.type]}${f.competition ? ` · ${f.competition}` : ''}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Crest-vs-crest hero scoreboard */}\n <Scoreboard fixture={f} variant=\"hero\" className=\"mb-8\" />\n\n {/* Availability / RSVP (BOFF-4134) — athlete's own response + coach roster view */}\n <div className=\"mb-8 grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_2fr]\">\n <AvailabilityRsvpControl teamId={f.homeTeamId} fixtureId={f.id} />\n <AvailabilityRosterPanel teamId={f.homeTeamId} fixtureId={f.id} />\n </div>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Notes</h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {canRecordResult && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Flag className=\"h-5 w-5\" /> Record result\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-6\">\n <p className=\"mb-5 text-sm text-text-secondary\">\n Recording a final score marks this fixture <strong>Completed</strong> and recomputes\n the competition standings for both teams.\n </p>\n <form onSubmit={handleRecord}>\n <div className=\"flex flex-wrap items-end justify-center gap-4 sm:gap-6\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={homeName} sport={f.homeTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n </div>\n <FormField label=\"Home\" htmlFor=\"homeScore\" className=\"w-24\">\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n\n <Minus className=\"mb-3 h-5 w-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n\n <div className=\"flex items-end gap-3\">\n <FormField label=\"Away\" htmlFor=\"awayScore\" className=\"w-24\">\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={awayName} sport={f.awayTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-6 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n Record result\n </button>\n </div>\n </form>\n {error && <p className=\"mt-3 text-center text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" /> View {f.competition} standings\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixtureFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureFormPage.tsx"],"sourcesContent":["/**\n * Fixture Schedule Form\n * @module atheletebridge/pages/fixtures\n *\n * Mirrors the concept mock: a GlassCard-framed two-column form with a segmented\n * fixture-type control (all five AthFixtureType values incl. SCRIMMAGE), team\n * crest previews, competition/venue comboboxes seeded from existing fixtures,\n * and iconized date/time inputs. Every field maps to AthScheduleFixtureInput.\n * Semantic tokens only, responsive.\n */\n\nimport { type KeyboardEvent, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Loader2,\n Save,\n Calendar,\n Clock,\n Handshake,\n Shield,\n Trophy,\n Network,\n Swords,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { LoadingState } from '../../components/LoadingState';\nimport { Crest } from '../../components/Crest';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { cn } from '../../utils/cn';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useScheduleFixture, useTeams, useFixtures } from '../../hooks/useAthleteBridgeApi';\nimport { toIsoOrUndefined } from '../../utils/datetime';\nimport type { AthFixtureType } from '../../types';\n\nconst TYPE_ICONS: Record<AthFixtureType, LucideIcon> = {\n FRIENDLY: Handshake,\n LEAGUE: Shield,\n CUP: Trophy,\n TOURNAMENT: Network,\n SCRIMMAGE: Swords,\n};\nconst TYPES = Object.keys(FIXTURE_TYPE_LABELS) as AthFixtureType[];\n\nexport function FixtureFormPage() {\n const [searchParams] = useSearchParams();\n const { navigateTo } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 100 } });\n const existingFixtures = useFixtures({ pagination: { limit: 100 } });\n const scheduleFixture = useScheduleFixture();\n\n const [homeTeamId, setHomeTeamId] = useState(searchParams.get('homeTeamId') ?? '');\n const [awayTeamId, setAwayTeamId] = useState('');\n const [type, setType] = useState<AthFixtureType>('FRIENDLY');\n const [competition, setCompetition] = useState('');\n const [round, setRound] = useState('');\n const [venue, setVenue] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [kickoffAt, setKickoffAt] = useState('');\n const [notes, setNotes] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const teamOptions = teams.data?.items ?? [];\n const homeTeam = teamOptions.find((t) => t.id === homeTeamId);\n const awayTeam = teamOptions.find((t) => t.id === awayTeamId);\n\n // Seed comboboxes from distinct values on existing fixtures (honest, derived).\n const { competitions, venues } = useMemo(() => {\n const comp = new Set<string>();\n const ven = new Set<string>();\n for (const f of existingFixtures.data?.items ?? []) {\n if (f.competition) comp.add(f.competition);\n if (f.venue) ven.add(f.venue);\n }\n return {\n competitions: Array.from(comp).sort(),\n venues: Array.from(ven).sort(),\n };\n }, [existingFixtures.data]);\n\n const handleTypeKey = (e: KeyboardEvent<HTMLDivElement>) => {\n const idx = TYPES.indexOf(type);\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n setType(TYPES[(idx + 1) % TYPES.length]);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n setType(TYPES[(idx - 1 + TYPES.length) % TYPES.length]);\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!homeTeamId) {\n setError('A home team is required.');\n return;\n }\n if (!scheduledAt) {\n setError('A scheduled date and time is required.');\n return;\n }\n if (awayTeamId && awayTeamId === homeTeamId) {\n setError('Home and away teams must be different.');\n return;\n }\n\n try {\n const created = await scheduleFixture.mutateAsync({\n homeTeamId,\n awayTeamId: awayTeamId || undefined,\n type,\n competition: competition.trim() || undefined,\n round: round.trim() || undefined,\n venue: venue.trim() || undefined,\n scheduledAt: new Date(scheduledAt).toISOString(),\n kickoffAt: toIsoOrUndefined(kickoffAt),\n notes: notes.trim() || undefined,\n });\n navigateTo(`/fixtures/${created.id}`);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to schedule fixture.');\n }\n };\n\n if (teams.isLoading) {\n return (\n <PageLayout title=\"Schedule fixture\">\n <LoadingState label=\"Loading teams…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Schedule fixture\"\n description=\"Set up a match between a home team and an optional away team.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-7\">\n {/* Home / away teams with crest preview */}\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Home team\" htmlFor=\"homeTeam\" required>\n <select\n id=\"homeTeam\"\n className={fieldInputClass}\n value={homeTeamId}\n onChange={(e) => setHomeTeamId(e.target.value)}\n >\n <option value=\"\">Select a team</option>\n {teamOptions.map((tm) => (\n <option key={tm.id} value={tm.id}>\n {tm.name}\n </option>\n ))}\n </select>\n {homeTeam ? (\n <div className=\"mt-2 flex items-center gap-2\">\n <Crest name={homeTeam.name} sport={homeTeam.sport} size={28} />\n <span className=\"truncate text-xs font-medium text-text-secondary\">\n {homeTeam.name}\n </span>\n </div>\n ) : null}\n </FormField>\n\n <FormField\n label=\"Away team\"\n htmlFor=\"awayTeam\"\n hint=\"Optional for trials / open events.\"\n >\n <select\n id=\"awayTeam\"\n className={fieldInputClass}\n value={awayTeamId}\n onChange={(e) => setAwayTeamId(e.target.value)}\n >\n <option value=\"\">No away team</option>\n {teamOptions.map((tm) => (\n <option key={tm.id} value={tm.id}>\n {tm.name}\n </option>\n ))}\n </select>\n {awayTeam ? (\n <div className=\"mt-2 flex items-center gap-2\">\n <Crest name={awayTeam.name} sport={awayTeam.sport} size={28} />\n <span className=\"truncate text-xs font-medium text-text-secondary\">\n {awayTeam.name}\n </span>\n </div>\n ) : null}\n </FormField>\n </div>\n\n {/* Type — segmented control (all five values incl. SCRIMMAGE) */}\n <div className=\"mt-5\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</span>\n <div\n role=\"radiogroup\"\n aria-label=\"Fixture type\"\n onKeyDown={handleTypeKey}\n className=\"grid grid-cols-2 gap-2 sm:grid-cols-5\"\n >\n {TYPES.map((tp) => {\n const Icon = TYPE_ICONS[tp];\n const active = type === tp;\n return (\n <button\n key={tp}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n onClick={() => setType(tp)}\n className={cn(\n 'flex items-center justify-center gap-1.5 rounded-lg border px-2.5 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n active\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary ring-1 ring-inset ring-action-primary-bg'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <Icon className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {FIXTURE_TYPE_LABELS[tp]}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Competition / round */}\n <div className=\"mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Competition\" htmlFor=\"competition\" hint=\"Used to group standings.\">\n <input\n id=\"competition\"\n list=\"ath-fixture-competitions\"\n className={fieldInputClass}\n value={competition}\n onChange={(e) => setCompetition(e.target.value)}\n placeholder=\"e.g. Spring League 2026\"\n />\n <datalist id=\"ath-fixture-competitions\">\n {competitions.map((c) => (\n <option key={c} value={c} />\n ))}\n </datalist>\n </FormField>\n\n <FormField label=\"Round\" htmlFor=\"round\">\n <input\n id=\"round\"\n className={fieldInputClass}\n value={round}\n onChange={(e) => setRound(e.target.value)}\n placeholder=\"e.g. Round 4\"\n />\n </FormField>\n </div>\n\n {/* Venue */}\n <div className=\"mt-5\">\n <FormField label=\"Venue\" htmlFor=\"venue\">\n <input\n id=\"venue\"\n list=\"ath-fixture-venues\"\n className={fieldInputClass}\n value={venue}\n onChange={(e) => setVenue(e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n <datalist id=\"ath-fixture-venues\">\n {venues.map((v) => (\n <option key={v} value={v} />\n ))}\n </datalist>\n </FormField>\n </div>\n\n {/* Schedule / kickoff */}\n <div className=\"mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Scheduled at\" htmlFor=\"scheduledAt\" required>\n <div className=\"relative\">\n <Calendar\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"scheduledAt\"\n type=\"datetime-local\"\n className={cn(fieldInputClass, 'pl-9')}\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n />\n </div>\n </FormField>\n\n <FormField label=\"Kick-off at\" htmlFor=\"kickoffAt\" hint=\"Optional refinement.\">\n <div className=\"relative\">\n <Clock\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"kickoffAt\"\n type=\"datetime-local\"\n className={cn(fieldInputClass, 'pl-9')}\n value={kickoffAt}\n onChange={(e) => setKickoffAt(e.target.value)}\n />\n </div>\n </FormField>\n </div>\n\n {/* Notes */}\n <div className=\"mt-5\">\n <FormField label=\"Notes\" htmlFor=\"notes\">\n <textarea\n id=\"notes\"\n className={fieldInputClass}\n rows={3}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder=\"Any extra context for this fixture.\"\n />\n </FormField>\n </div>\n\n {error && <p className=\"mt-5 text-sm text-status-error-text\">{error}</p>}\n\n {/* Footer */}\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={scheduleFixture.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {scheduleFixture.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Schedule fixture\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsCA,IAAM,IAAiD;CACrD,UAAU;CACV,QAAQ;CACR,KAAK;CACL,YAAY;CACZ,WAAW;CACZ,EACK,IAAQ,OAAO,KAAK,EAAoB;AAE9C,SAAgB,IAAkB;CAChC,IAAM,CAAC,KAAgB,GAAiB,EAClC,EAAE,kBAAe,GAAkB,EAEnC,IAAQ,EAAS,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE,CAAC,EAChD,IAAmB,EAAY,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE,CAAC,EAC9D,IAAkB,GAAoB,EAEtC,CAAC,GAAY,KAAiB,EAAS,EAAa,IAAI,aAAa,IAAI,GAAG,EAC5E,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAM,KAAW,EAAyB,WAAW,EACtD,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAW,KAAgB,EAAS,GAAG,EACxC,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAc,EAAM,MAAM,SAAS,EAAE,EACrC,IAAW,EAAY,MAAM,MAAM,EAAE,OAAO,EAAW,EACvD,IAAW,EAAY,MAAM,MAAM,EAAE,OAAO,EAAW,EAGvD,EAAE,iBAAc,eAAW,QAAc;EAC7C,IAAM,oBAAO,IAAI,KAAa,EACxB,oBAAM,IAAI,KAAa;AAC7B,OAAK,IAAM,KAAK,EAAiB,MAAM,SAAS,EAAE,CAEhD,CADI,EAAE,eAAa,EAAK,IAAI,EAAE,YAAY,EACtC,EAAE,SAAO,EAAI,IAAI,EAAE,MAAM;AAE/B,SAAO;GACL,cAAc,MAAM,KAAK,EAAK,CAAC,MAAM;GACrC,QAAQ,MAAM,KAAK,EAAI,CAAC,MAAM;GAC/B;IACA,CAAC,EAAiB,KAAK,CAAC;AAwD3B,QARI,EAAM,YAEN,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAc,OAAM,kBAAmB,CAAA;EAC5B,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,YAAY;GACtC,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,UAC1B;;YAGX,kBAAC,QAAD;GAAM,UAzDW,OAAO,MAAuB;AAIjD,QAHA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EAEV,CAAC,GAAY;AACf,OAAS,2BAA2B;AACpC;;AAEF,QAAI,CAAC,GAAa;AAChB,OAAS,yCAAyC;AAClD;;AAEF,QAAI,KAAc,MAAe,GAAY;AAC3C,OAAS,yCAAyC;AAClD;;AAGF,QAAI;AAYF,OAAW,cAXK,MAAM,EAAgB,YAAY;MAChD;MACA,YAAY,KAAc,KAAA;MAC1B;MACA,aAAa,EAAY,MAAM,IAAI,KAAA;MACnC,OAAO,EAAM,MAAM,IAAI,KAAA;MACvB,OAAO,EAAM,MAAM,IAAI,KAAA;MACvB,aAAa,IAAI,KAAK,EAAY,CAAC,aAAa;MAChD,WAAW,EAAiB,EAAU;MACtC,OAAO,EAAM,MAAM,IAAI,KAAA;MACxB,CAAC,EAC8B,KAAK;aAC9B,GAAK;AACZ,OAAS,aAAe,QAAQ,EAAI,UAAU,8BAA8B;;;GA0B9C,WAAU;aACtC,kBAAC,GAAD;IAAW,WAAU;IAAU,WAAU;cAAzC;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAW,OAAM;OAAY,SAAQ;OAAW,UAAA;iBAAhD,CACE,kBAAC,UAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;kBAJhD,CAME,kBAAC,UAAD;SAAQ,OAAM;mBAAG;SAAsB,CAAA,EACtC,EAAY,KAAK,MAChB,kBAAC,UAAD;SAAoB,OAAO,EAAG;mBAC3B,EAAG;SACG,EAFI,EAAG,GAEP,CACT,CACK;WACR,IACC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,MAAM,EAAS;SAAM,OAAO,EAAS;SAAO,MAAM;SAAM,CAAA,EAC/D,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAS;SACL,CAAA,CACH;YACJ,KACM;UAEZ,kBAAC,GAAD;OACE,OAAM;OACN,SAAQ;OACR,MAAK;iBAHP,CAKE,kBAAC,UAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;kBAJhD,CAME,kBAAC,UAAD;SAAQ,OAAM;mBAAG;SAAqB,CAAA,EACrC,EAAY,KAAK,MAChB,kBAAC,UAAD;SAAoB,OAAO,EAAG;mBAC3B,EAAG;SACG,EAFI,EAAG,GAEP,CACT,CACK;WACR,IACC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,MAAM,EAAS;SAAM,OAAO,EAAS;SAAO,MAAM;SAAM,CAAA,EAC/D,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAS;SACL,CAAA,CACH;YACJ,KACM;SACR;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAqD;OAAW,CAAA,EAChF,kBAAC,OAAD;OACE,MAAK;OACL,cAAW;OACX,YAnIW,MAAqC;QAC1D,IAAM,IAAM,EAAM,QAAQ,EAAK;AAC/B,QAAI,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,eACtC,EAAE,gBAAgB,EAClB,EAAQ,GAAO,IAAM,KAAK,EAAM,QAAQ,KAC/B,EAAE,QAAQ,eAAe,EAAE,QAAQ,eAC5C,EAAE,gBAAgB,EAClB,EAAQ,GAAO,IAAM,IAAI,EAAM,UAAU,EAAM,QAAQ;;OA6H/C,WAAU;iBAET,EAAM,KAAK,MAAO;QACjB,IAAM,IAAO,EAAW,IAClB,IAAS,MAAS;AACxB,eACE,kBAAC,UAAD;SAEE,MAAK;SACL,MAAK;SACL,gBAAc;SACd,UAAU,IAAS,IAAI;SACvB,eAAe,EAAQ,EAAG;SAC1B,WAAW,EACT,+MACA,IACI,sHACA,4DACL;mBAZH,CAcE,kBAAC,GAAD;UAAM,WAAU;UAAmB,eAAY;UAAS,CAAA,EACvD,EAAoB,GACd;WAfF,EAeE;SAEX;OACE,CAAA,CACF;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAW,OAAM;OAAc,SAAQ;OAAc,MAAK;iBAA1D,CACE,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,WAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,aAAY;QACZ,CAAA,EACF,kBAAC,YAAD;QAAU,IAAG;kBACV,EAAa,KAAK,MACjB,kBAAC,UAAD,EAAgB,OAAO,GAAK,EAAf,EAAe,CAC5B;QACO,CAAA,CACD;UAEZ,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,SAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;QACzC,aAAY;QACZ,CAAA;OACQ,CAAA,CACR;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAAjC,CACE,kBAAC,SAAD;QACE,IAAG;QACH,MAAK;QACL,WAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;QACzC,aAAY;QACZ,CAAA,EACF,kBAAC,YAAD;QAAU,IAAG;kBACV,GAAO,KAAK,MACX,kBAAC,UAAD,EAAgB,OAAO,GAAK,EAAf,EAAe,CAC5B;QACO,CAAA,CACD;;MACR,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAW,OAAM;OAAe,SAAQ;OAAc,UAAA;iBACpD,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SACE,WAAU;SACV,eAAY;SACZ,CAAA,EACF,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,WAAW,EAAG,GAAiB,OAAO;SACtC,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;SAC/C,CAAA,CACE;;OACI,CAAA,EAEZ,kBAAC,GAAD;OAAW,OAAM;OAAc,SAAQ;OAAY,MAAK;iBACtD,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SACE,WAAU;SACV,eAAY;SACZ,CAAA,EACF,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,WAAW,EAAG,GAAiB,OAAO;SACtC,OAAO;SACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;SAC7C,CAAA,CACE;;OACI,CAAA,CACR;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,YAAD;QACE,IAAG;QACH,WAAW;QACX,MAAM;QACN,OAAO;QACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;QACzC,aAAY;QACZ,CAAA;OACQ,CAAA;MACR,CAAA;KAEL,KAAS,kBAAC,KAAD;MAAG,WAAU;gBAAuC;MAAU,CAAA;KAGxE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,YAAY;OACtC,WAAU;iBACX;OAEQ,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,EAAgB;OAC1B,WAAU;iBAHZ,CAKG,EAAgB,YACf,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC5B,mBAEK;SACL;;KACI;;GACP,CAAA;EACI,CAAA"}
|
|
1
|
+
{"version":3,"file":"FixtureFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureFormPage.tsx"],"sourcesContent":["/**\n * Fixture Schedule Form\n * @module atheletebridge/pages/fixtures\n *\n * Mirrors the concept mock: a GlassCard-framed two-column form with a segmented\n * fixture-type control (all five AthFixtureType values incl. SCRIMMAGE), team\n * crest previews, competition/venue comboboxes seeded from existing fixtures,\n * and iconized date/time inputs. Every field maps to AthScheduleFixtureInput.\n * Semantic tokens only, responsive.\n */\n\nimport { type KeyboardEvent, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Loader2,\n Save,\n Calendar,\n Clock,\n Handshake,\n Shield,\n Trophy,\n Network,\n Swords,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { LoadingState } from '../../components/LoadingState';\nimport { Crest } from '../../components/Crest';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { cn } from '../../utils/cn';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useScheduleFixture, useTeams, useFixtures } from '../../hooks/useAthleteBridgeApi';\nimport { toIsoOrUndefined } from '../../utils/datetime';\nimport type { AthFixtureType } from '../../types';\n\nconst TYPE_ICONS: Record<AthFixtureType, LucideIcon> = {\n FRIENDLY: Handshake,\n LEAGUE: Shield,\n CUP: Trophy,\n TOURNAMENT: Network,\n SCRIMMAGE: Swords,\n};\nconst TYPES = Object.keys(FIXTURE_TYPE_LABELS) as AthFixtureType[];\n\nexport function FixtureFormPage() {\n const [searchParams] = useSearchParams();\n const { navigateTo } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 100 } });\n const existingFixtures = useFixtures({ pagination: { limit: 100 } });\n const scheduleFixture = useScheduleFixture();\n\n const [homeTeamId, setHomeTeamId] = useState(searchParams.get('homeTeamId') ?? '');\n const [awayTeamId, setAwayTeamId] = useState('');\n const [type, setType] = useState<AthFixtureType>('FRIENDLY');\n const [competition, setCompetition] = useState('');\n const [round, setRound] = useState('');\n const [venue, setVenue] = useState('');\n const [scheduledAt, setScheduledAt] = useState('');\n const [kickoffAt, setKickoffAt] = useState('');\n const [notes, setNotes] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const teamOptions = teams.data?.items ?? [];\n const homeTeam = teamOptions.find((t) => t.id === homeTeamId);\n const awayTeam = teamOptions.find((t) => t.id === awayTeamId);\n\n // Seed comboboxes from distinct values on existing fixtures (honest, derived).\n const { competitions, venues } = useMemo(() => {\n const comp = new Set<string>();\n const ven = new Set<string>();\n for (const f of existingFixtures.data?.items ?? []) {\n if (f.competition) comp.add(f.competition);\n if (f.venue) ven.add(f.venue);\n }\n return {\n competitions: Array.from(comp).sort(),\n venues: Array.from(ven).sort(),\n };\n }, [existingFixtures.data]);\n\n const handleTypeKey = (e: KeyboardEvent<HTMLDivElement>) => {\n const idx = TYPES.indexOf(type);\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n e.preventDefault();\n setType(TYPES[(idx + 1) % TYPES.length]);\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n e.preventDefault();\n setType(TYPES[(idx - 1 + TYPES.length) % TYPES.length]);\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!homeTeamId) {\n setError('A home team is required.');\n return;\n }\n if (!scheduledAt) {\n setError('A scheduled date and time is required.');\n return;\n }\n if (awayTeamId && awayTeamId === homeTeamId) {\n setError('Home and away teams must be different.');\n return;\n }\n\n try {\n const created = await scheduleFixture.mutateAsync({\n homeTeamId,\n awayTeamId: awayTeamId || undefined,\n type,\n competition: competition.trim() || undefined,\n round: round.trim() || undefined,\n venue: venue.trim() || undefined,\n scheduledAt: new Date(scheduledAt).toISOString(),\n kickoffAt: toIsoOrUndefined(kickoffAt),\n notes: notes.trim() || undefined,\n });\n navigateTo(`/fixtures/${created.id}`);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to schedule fixture.');\n }\n };\n\n if (teams.isLoading) {\n return (\n <PageLayout title=\"Schedule fixture\">\n <LoadingState label=\"Loading teams…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Schedule fixture\"\n description=\"Set up a match between a home team and an optional away team.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-7\">\n {/* Home / away teams with crest preview */}\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Home team\" htmlFor=\"homeTeam\" required>\n <select\n id=\"homeTeam\"\n className={fieldInputClass}\n value={homeTeamId}\n onChange={(e) => setHomeTeamId(e.target.value)}\n >\n <option value=\"\">Select a team</option>\n {teamOptions.map((tm) => (\n <option key={tm.id} value={tm.id}>\n {tm.name}\n </option>\n ))}\n </select>\n {homeTeam ? (\n <div className=\"mt-2 flex items-center gap-2\">\n <Crest name={homeTeam.name} sport={homeTeam.sport} size={28} />\n <span className=\"truncate text-xs font-medium text-text-secondary\">\n {homeTeam.name}\n </span>\n </div>\n ) : null}\n </FormField>\n\n <FormField\n label=\"Away team\"\n htmlFor=\"awayTeam\"\n hint=\"Optional for trials / open events.\"\n >\n <select\n id=\"awayTeam\"\n className={fieldInputClass}\n value={awayTeamId}\n onChange={(e) => setAwayTeamId(e.target.value)}\n >\n <option value=\"\">No away team</option>\n {teamOptions.map((tm) => (\n <option key={tm.id} value={tm.id}>\n {tm.name}\n </option>\n ))}\n </select>\n {awayTeam ? (\n <div className=\"mt-2 flex items-center gap-2\">\n <Crest name={awayTeam.name} sport={awayTeam.sport} size={28} />\n <span className=\"truncate text-xs font-medium text-text-secondary\">\n {awayTeam.name}\n </span>\n </div>\n ) : null}\n </FormField>\n </div>\n\n {/* Type — segmented control (all five values incl. SCRIMMAGE) */}\n <div className=\"mt-5\">\n <span className=\"mb-1.5 block text-sm font-medium text-text-primary\">Type</span>\n <div\n role=\"radiogroup\"\n aria-label=\"Fixture type\"\n onKeyDown={handleTypeKey}\n className=\"grid grid-cols-2 gap-2 sm:grid-cols-5\"\n >\n {TYPES.map((tp) => {\n const Icon = TYPE_ICONS[tp];\n const active = type === tp;\n return (\n <button\n key={tp}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n onClick={() => setType(tp)}\n className={cn(\n 'flex items-center justify-center gap-1.5 rounded-lg border px-2.5 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n active\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-primary ring-1 ring-inset ring-action-primary-bg'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <Icon className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {FIXTURE_TYPE_LABELS[tp]}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Competition / round */}\n <div className=\"mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Competition\" htmlFor=\"competition\" hint=\"Used to group standings.\">\n <input\n id=\"competition\"\n list=\"ath-fixture-competitions\"\n className={fieldInputClass}\n value={competition}\n onChange={(e) => setCompetition(e.target.value)}\n placeholder=\"e.g. Spring League 2026\"\n />\n <datalist id=\"ath-fixture-competitions\">\n {competitions.map((c) => (\n <option key={c} value={c} />\n ))}\n </datalist>\n </FormField>\n\n <FormField label=\"Round\" htmlFor=\"round\">\n <input\n id=\"round\"\n className={fieldInputClass}\n value={round}\n onChange={(e) => setRound(e.target.value)}\n placeholder=\"e.g. Round 4\"\n />\n </FormField>\n </div>\n\n {/* Venue */}\n <div className=\"mt-5\">\n <FormField label=\"Venue\" htmlFor=\"venue\">\n <input\n id=\"venue\"\n list=\"ath-fixture-venues\"\n className={fieldInputClass}\n value={venue}\n onChange={(e) => setVenue(e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n <datalist id=\"ath-fixture-venues\">\n {venues.map((v) => (\n <option key={v} value={v} />\n ))}\n </datalist>\n </FormField>\n </div>\n\n {/* Schedule / kickoff */}\n <div className=\"mt-5 grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Scheduled at\" htmlFor=\"scheduledAt\" required>\n <div className=\"relative\">\n <Calendar\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"scheduledAt\"\n type=\"datetime-local\"\n className={cn(fieldInputClass, 'pl-9')}\n value={scheduledAt}\n onChange={(e) => setScheduledAt(e.target.value)}\n />\n </div>\n </FormField>\n\n <FormField label=\"Kick-off at\" htmlFor=\"kickoffAt\" hint=\"Optional refinement.\">\n <div className=\"relative\">\n <Clock\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\"\n aria-hidden=\"true\"\n />\n <input\n id=\"kickoffAt\"\n type=\"datetime-local\"\n className={cn(fieldInputClass, 'pl-9')}\n value={kickoffAt}\n onChange={(e) => setKickoffAt(e.target.value)}\n />\n </div>\n </FormField>\n </div>\n\n {/* Notes */}\n <div className=\"mt-5\">\n <FormField label=\"Notes\" htmlFor=\"notes\">\n <textarea\n id=\"notes\"\n className={fieldInputClass}\n rows={3}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder=\"Any extra context for this fixture.\"\n />\n </FormField>\n </div>\n\n {error && <p className=\"mt-5 text-sm text-status-error-text\">{error}</p>}\n\n {/* Footer */}\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={scheduleFixture.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {scheduleFixture.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Schedule fixture\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixturesListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixturesListPage.tsx"],"sourcesContent":["/**\n * Fixtures List\n * @module atheletebridge/pages/fixtures\n *\n * Crest-vs-crest FixtureCard grid with status, team and type filters. The team\n * filter is server-backed (athListFixtures accepts teamId); the type filter is\n * applied over the loaded page. A \"Tournament bracket\" shortcut links to the\n * tournaments view. Semantic tokens only, responsive.\n */\n\nimport { useMemo, useState } from 'react';\nimport { CalendarDays, Plus, Trophy } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FixtureCard } from '../../components/FixtureCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { FIXTURE_TYPE_LABELS, FIXTURE_TYPE_OPTIONS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixtures, useTeams } from '../../hooks/useAthleteBridgeApi';\nimport type { AthFixtureStatus, AthFixtureType } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthFixtureStatus; label: string }> = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LIVE', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'POSTPONED', label: 'Postponed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst TYPE_OPTS: ReadonlyArray<{ value: AthFixtureType; label: string }> = FIXTURE_TYPE_OPTIONS.map(\n (t) => ({ value: t, label: FIXTURE_TYPE_LABELS[t] })\n);\n\nexport function FixturesListPage() {\n const { navigateTo } = useAthleteBridge();\n const [status, setStatus] = useState<AthFixtureStatus | ''>('');\n const [teamId, setTeamId] = useState<string>('');\n const [type, setType] = useState<AthFixtureType | ''>('');\n const [page, setPage] = useState(0);\n\n const teams = useTeams({ pagination: { limit: 100 } });\n const teamOpts = useMemo(\n () => (teams.data?.items ?? []).map((t) => ({ value: t.id, label: t.name })),\n [teams.data]\n );\n\n const fixtures = useFixtures({\n status: status || undefined,\n teamId: teamId || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = useMemo(() => {\n const raw = fixtures.data?.items ?? [];\n return type ? raw.filter((f) => f.type === type) : raw;\n }, [fixtures.data, type]);\n const hasMore = fixtures.data?.pageInfo.hasMore ?? false;\n const hasFilters = Boolean(status || teamId || type);\n\n const resetPage = () => setPage(0);\n\n return (\n <PageLayout\n title=\"Fixtures\"\n description=\"Matches across friendlies, league games, cups, tournaments and scrimmages.\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/tournaments')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" /> Tournament bracket\n </button>\n <button\n type=\"button\"\n data-tour=\"fixtures-create\"\n onClick={() => navigateTo('/fixtures/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> Schedule fixture\n </button>\n </div>\n }\n >\n <div data-tour=\"fixtures-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n resetPage();\n }}\n options={STATUS_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by team\"\n allLabel=\"All teams\"\n value={teamId}\n onChange={(v) => {\n setTeamId(v);\n resetPage();\n }}\n options={teamOpts}\n />\n <FilterSelect\n ariaLabel=\"Filter by type\"\n allLabel=\"All types\"\n value={type}\n onChange={(v) => setType(v)}\n options={TYPE_OPTS}\n />\n </div>\n\n {fixtures.isLoading ? (\n <LoadingState label=\"Loading fixtures…\" />\n ) : fixtures.isError ? (\n <ErrorState onRetry={() => void fixtures.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No fixtures found\"\n description={\n hasFilters\n ? 'Try clearing your filters to see all fixtures.'\n : 'Schedule a match to start tracking results and standings.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Schedule fixture\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {items.map((fixture) => (\n <FixtureCard\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </div>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,IAAM,IAAY,IAEZ,IAAyE;CAC7E;EAAE,OAAO;EAAa,OAAO;
|
|
1
|
+
{"version":3,"file":"FixturesListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixturesListPage.tsx"],"sourcesContent":["/**\n * Fixtures List\n * @module atheletebridge/pages/fixtures\n *\n * Crest-vs-crest FixtureCard grid with status, team and type filters. The team\n * filter is server-backed (athListFixtures accepts teamId); the type filter is\n * applied over the loaded page. A \"Tournament bracket\" shortcut links to the\n * tournaments view. Semantic tokens only, responsive.\n */\n\nimport { useMemo, useState } from 'react';\nimport { CalendarDays, Plus, Trophy } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { FixtureCard } from '../../components/FixtureCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { FIXTURE_TYPE_LABELS, FIXTURE_TYPE_OPTIONS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixtures, useTeams } from '../../hooks/useAthleteBridgeApi';\nimport type { AthFixtureStatus, AthFixtureType } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthFixtureStatus; label: string }> = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LIVE', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'POSTPONED', label: 'Postponed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst TYPE_OPTS: ReadonlyArray<{ value: AthFixtureType; label: string }> = FIXTURE_TYPE_OPTIONS.map(\n (t) => ({ value: t, label: FIXTURE_TYPE_LABELS[t] })\n);\n\nexport function FixturesListPage() {\n const { navigateTo } = useAthleteBridge();\n const [status, setStatus] = useState<AthFixtureStatus | ''>('');\n const [teamId, setTeamId] = useState<string>('');\n const [type, setType] = useState<AthFixtureType | ''>('');\n const [page, setPage] = useState(0);\n\n const teams = useTeams({ pagination: { limit: 100 } });\n const teamOpts = useMemo(\n () => (teams.data?.items ?? []).map((t) => ({ value: t.id, label: t.name })),\n [teams.data]\n );\n\n const fixtures = useFixtures({\n status: status || undefined,\n teamId: teamId || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = useMemo(() => {\n const raw = fixtures.data?.items ?? [];\n return type ? raw.filter((f) => f.type === type) : raw;\n }, [fixtures.data, type]);\n const hasMore = fixtures.data?.pageInfo.hasMore ?? false;\n const hasFilters = Boolean(status || teamId || type);\n\n const resetPage = () => setPage(0);\n\n return (\n <PageLayout\n title=\"Fixtures\"\n description=\"Matches across friendlies, league games, cups, tournaments and scrimmages.\"\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/tournaments')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" /> Tournament bracket\n </button>\n <button\n type=\"button\"\n data-tour=\"fixtures-create\"\n onClick={() => navigateTo('/fixtures/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> Schedule fixture\n </button>\n </div>\n }\n >\n <div data-tour=\"fixtures-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n resetPage();\n }}\n options={STATUS_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by team\"\n allLabel=\"All teams\"\n value={teamId}\n onChange={(v) => {\n setTeamId(v);\n resetPage();\n }}\n options={teamOpts}\n />\n <FilterSelect\n ariaLabel=\"Filter by type\"\n allLabel=\"All types\"\n value={type}\n onChange={(v) => setType(v)}\n options={TYPE_OPTS}\n />\n </div>\n\n {fixtures.isLoading ? (\n <LoadingState label=\"Loading fixtures…\" />\n ) : fixtures.isError ? (\n <ErrorState onRetry={() => void fixtures.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No fixtures found\"\n description={\n hasFilters\n ? 'Try clearing your filters to see all fixtures.'\n : 'Schedule a match to start tracking results and standings.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Schedule fixture\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {items.map((fixture) => (\n <FixtureCard\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </div>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OnboardingPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/onboarding/OnboardingPage.tsx"],"sourcesContent":["/**\n * First-run onboarding wizard\n * @module atheletebridge/pages/onboarding\n *\n * The AthleteBridge first-run flow: three steps — your sport → create your team\n * → your role — landing the user on a usable team page. Design reference is\n * mockup 26 (`atheletebridge-specs/product/mockups`), realised entirely with\n * fe-libs semantic tokens (the mockup's orange→red brand ramp maps to\n * `--color-action-primary-bg` blended toward `--color-accent-purple`, exactly as\n * the dashboard hero CTA does; the mockup's teal-green \"complete\" maps to\n * `--color-accent-teal`).\n *\n * Scope is deliberately narrow — sport, team, role. Workspace/organization\n * creation is NOT part of this flow: `global-tenant-svc` owns it and it already\n * precedes product entry, so duplicating it here would fork a platform module\n * (see `workspaces-specs/business/strategy/module-ownership`).\n *\n * Data layer (all real, nothing mocked):\n * - `useCreateTeam` → `athCreateTeam` — creates the squad from step 2.\n * - `useAddMember` → `athAddMember` — puts the calling user on that squad's\n * roster with the role picked in step 3. Best-effort: the team is the primary\n * outcome and is already persisted, so a roster hiccup must not strand the\n * user mid-flow.\n *\n * Unlike the entity pages this is a calm, centered flow rather than the admin\n * `PageLayout` header, and it renders no product wordmark — the app shell chrome\n * already carries AthleteBridge branding above this surface.\n */\n\nimport { useCallback, useMemo, useState, type FC, type FormEvent } from 'react';\nimport { ArrowLeft, ArrowRight, Check, Loader2, Users } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { FormField, fieldInputClass } from '../../components/form';\nimport {\n MEMBER_ROLE_LABELS,\n MEMBER_ROLE_OPTIONS,\n SPORT_ICONS,\n SPORT_LABELS,\n SPORT_OPTIONS,\n} from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAddMember, useCreateTeam } from '../../hooks/useAthleteBridgeApi';\nimport type { AthMemberRole, AthSport } from '../../types';\nimport { cn } from '../../utils/cn';\nimport { useAbTranslate } from '../../i18n';\n\n// ============================================================================\n// Static option catalogues\n// ============================================================================\n\n/**\n * `AthTeam.ageGroup` is a free-text column on the subgraph; these are the\n * conventional squad bands offered as a shortcut. \"Senior\" and \"Masters\" cover\n * non-age-banded squads, and the empty value leaves the column unset.\n */\nconst AGE_GROUPS: ReadonlyArray<{ value: string; label: string }> = [\n { value: '', label: 'Not age-banded' },\n { value: 'U8', label: 'U8' },\n { value: 'U10', label: 'U10' },\n { value: 'U12', label: 'U12' },\n { value: 'U14', label: 'U14' },\n { value: 'U16', label: 'U16' },\n { value: 'U18', label: 'U18' },\n { value: 'U21', label: 'U21' },\n { value: 'SENIOR', label: 'Senior' },\n { value: 'MASTERS', label: 'Masters' },\n];\n\n/**\n * `AthTeam.season` is also free text. Options are derived from the current date\n * rather than hard-coded, so the list never goes stale: the calendar year, the\n * split-year season either side of it, and the previous calendar year.\n */\nfunction buildSeasonOptions(now: Date): ReadonlyArray<{ value: string; label: string }> {\n const year = now.getFullYear();\n const short = (y: number): string => String(y % 100).padStart(2, '0');\n return [\n { value: '', label: 'No season' },\n { value: String(year), label: String(year) },\n { value: `${year}/${short(year + 1)}`, label: `${year}/${short(year + 1)}` },\n { value: `${year - 1}/${short(year)}`, label: `${year - 1}/${short(year)}` },\n { value: String(year - 1), label: String(year - 1) },\n ];\n}\n\nconst TOTAL_STEPS = 3;\nconst TEAM_NAME_LIMIT = 120;\n\ntype StepNumber = 1 | 2 | 3;\n\nconst STEP_LABELS: Record<StepNumber, string> = {\n 1: 'Your sport',\n 2: 'Create your team',\n 3: 'Your role',\n};\n\n// ============================================================================\n// Shared token-only styling\n// ============================================================================\n\n/**\n * Brand gradient — the same orange→purple ramp the dashboard hero CTA uses, so\n * the wizard's active step and primary action read as one brand with the rest of\n * the product. Token-only (no raw colors).\n */\nconst BRAND_GRADIENT =\n 'bg-[linear-gradient(120deg,var(--color-action-primary-bg),color-mix(in_oklab,var(--color-action-primary-bg)_62%,var(--color-accent-purple)),var(--color-action-primary-bg))]';\n\nconst PRIMARY_BUTTON = cn(\n 'inline-flex items-center justify-center gap-1.5 rounded-lg px-5 py-2.5 text-sm font-semibold',\n 'text-action-primary-text shadow-elevation-1 transition-[filter,box-shadow]',\n 'hover:shadow-elevation-2 hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-60',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n BRAND_GRADIENT\n);\n\nconst SECONDARY_BUTTON = cn(\n 'inline-flex items-center justify-center gap-1.5 rounded-lg border border-border-seam px-5 py-2.5',\n 'text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n);\n\nconst GHOST_BUTTON = cn(\n 'rounded-lg px-4 py-2.5 text-sm font-medium text-text-secondary transition-colors',\n 'hover:text-text-primary focus-visible:outline-none focus-visible:ring-2',\n 'focus-visible:ring-[var(--color-focus-ring)]'\n);\n\nconst CHIP_BASE = cn(\n 'inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-medium transition-all',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n 'motion-safe:hover:-translate-y-0.5'\n);\n\nconst CHIP_IDLE =\n 'border-border-seam bg-bg-surface text-text-secondary hover:border-border-strong hover:text-text-primary';\n\nconst CHIP_ACTIVE = cn(\n 'border-transparent text-action-primary-text shadow-elevation-1',\n BRAND_GRADIENT\n);\n\n// ============================================================================\n// Stepper\n// ============================================================================\n\ninterface StepperProps {\n current: StepNumber;\n}\n\n/**\n * The mockup's three-node stepper: teal-green + tick for a completed step, brand\n * gradient for the active step, hollow outline for an upcoming one. The nodes are\n * decorative — the accessible state is carried by the `progressbar` wrapper and\n * the visible step caption below the card.\n */\nconst Stepper: FC<StepperProps> = ({ current }) => (\n <div\n className=\"flex items-start justify-center\"\n role=\"progressbar\"\n aria-valuemin={1}\n aria-valuemax={TOTAL_STEPS}\n aria-valuenow={current}\n aria-valuetext={`Step ${current} of ${TOTAL_STEPS}: ${STEP_LABELS[current]}`}\n aria-label=\"Onboarding progress\"\n >\n {([1, 2, 3] as StepNumber[]).map((step, index) => {\n const done = step < current;\n const active = step === current;\n return (\n <div key={step} className=\"flex items-start\" aria-hidden=\"true\">\n {index > 0 && (\n <span\n className={cn(\n 'mt-6 h-px w-10 shrink-0 transition-colors sm:w-20',\n step <= current ? 'bg-[var(--color-accent-teal)]' : 'bg-border-seam'\n )}\n />\n )}\n <div className=\"flex w-24 flex-col items-center gap-2 sm:w-32\">\n <span\n className={cn(\n 'flex h-12 w-12 items-center justify-center rounded-full text-base font-semibold transition-all',\n done && 'bg-[var(--color-accent-teal)] text-[var(--color-bg-canvas)]',\n active && cn('text-action-primary-text shadow-elevation-2', BRAND_GRADIENT),\n !done && !active && 'border border-border-strong text-text-muted'\n )}\n >\n {done ? <Check className=\"h-5 w-5\" /> : step}\n </span>\n <span\n className={cn(\n 'text-center text-sm',\n active ? 'font-semibold text-text-primary' : 'text-text-secondary'\n )}\n >\n {STEP_LABELS[step]}\n </span>\n </div>\n </div>\n );\n })}\n </div>\n);\n\n// ============================================================================\n// Sport chips\n// ============================================================================\n\ninterface SportChipsProps {\n value: AthSport | null;\n onSelect: (sport: AthSport) => void;\n size: 'lg' | 'sm';\n}\n\nconst SportChips: FC<SportChipsProps> = ({ value, onSelect, size }) => (\n <div className=\"flex flex-wrap gap-2\" role=\"group\" aria-label=\"Sport\">\n {SPORT_OPTIONS.map((sport) => {\n const Icon = SPORT_ICONS[sport];\n const active = value === sport;\n return (\n <button\n key={sport}\n type=\"button\"\n onClick={() => onSelect(sport)}\n aria-pressed={active}\n className={cn(\n CHIP_BASE,\n size === 'lg' && 'px-5 py-2.5 text-base',\n active ? CHIP_ACTIVE : CHIP_IDLE\n )}\n >\n <Icon className={size === 'lg' ? 'h-5 w-5' : 'h-4 w-4'} aria-hidden=\"true\" />\n {SPORT_LABELS[sport]}\n </button>\n );\n })}\n </div>\n);\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport const OnboardingPage: FC = () => {\n const tr = useAbTranslate();\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const createTeam = useCreateTeam();\n const addMember = useAddMember();\n\n const [step, setStep] = useState<StepNumber>(1);\n const [sport, setSport] = useState<AthSport | null>(null);\n const [teamName, setTeamName] = useState('');\n const [ageGroup, setAgeGroup] = useState('');\n const [season, setSeason] = useState('');\n const [role, setRole] = useState<AthMemberRole | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const seasonOptions = useMemo(() => buildSeasonOptions(new Date()), []);\n\n const busy = createTeam.isPending || addMember.isPending;\n\n const goDashboard = useCallback(() => navigateTo('/'), [navigateTo]);\n\n const handleBack = useCallback(() => {\n setError(null);\n setStep((prev) => (prev === 1 ? 1 : ((prev - 1) as StepNumber)));\n }, []);\n\n const handleSelectSport = useCallback((next: AthSport) => {\n setSport(next);\n setError(null);\n }, []);\n\n /**\n * Step 1/2 advance. Step 3 submits, so this only guards the two picker steps.\n */\n const handleContinue = useCallback(() => {\n if (step === 1) {\n if (!sport) {\n setError(tr('atheletebridge.pages.onboarding.errorSport', 'Pick a sport to continue.'));\n return;\n }\n setError(null);\n setStep(2);\n return;\n }\n if (!teamName.trim()) {\n setError(\n tr('atheletebridge.pages.onboarding.errorTeamName', 'Give your team a name to continue.')\n );\n return;\n }\n setError(null);\n setStep(3);\n }, [step, sport, teamName, tr]);\n\n /**\n * Final submit — creates the team, then (best-effort) puts the calling user on\n * its roster with the chosen role, and lands them on the new team page.\n */\n const handleFinish = useCallback(\n async (event: FormEvent) => {\n event.preventDefault();\n if (!sport || !role || !teamName.trim()) {\n setError(tr('atheletebridge.pages.onboarding.errorRole', 'Pick your role to finish.'));\n return;\n }\n setError(null);\n\n try {\n const team = await createTeam.mutateAsync({\n name: teamName.trim(),\n sport,\n status: 'FORMING',\n ageGroup: ageGroup || undefined,\n season: season || undefined,\n });\n\n if (currentUser?.id) {\n const fullName = [currentUser.firstName, currentUser.lastName]\n .filter((part): part is string => Boolean(part))\n .join(' ')\n .trim();\n const displayName = currentUser.name ?? (fullName || currentUser.email);\n try {\n await addMember.mutateAsync({\n teamId: team.id,\n userId: currentUser.id,\n displayName,\n role,\n status: 'ACTIVE',\n });\n } catch {\n // The team is created and is the primary outcome — a roster failure\n // must not strand the user in the wizard. They can add themselves\n // from the team page.\n }\n }\n\n navigateTo(`/teams/${team.id}`);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n 'atheletebridge.pages.onboarding.errorCreate',\n \"We couldn't create your team. Please try again.\"\n )\n );\n }\n },\n [sport, role, teamName, ageGroup, season, currentUser, createTeam, addMember, navigateTo, tr]\n );\n\n const heading =\n step === 1\n ? tr('atheletebridge.pages.onboarding.step1Title', 'What do you coach?')\n : step === 2\n ? tr('atheletebridge.pages.onboarding.step2Title', 'Create your first team')\n : tr('atheletebridge.pages.onboarding.step3Title', \"What's your role?\");\n\n const subheading =\n step === 1\n ? tr(\n 'atheletebridge.pages.onboarding.step1Subtitle',\n 'Your sport shapes the squads, fixtures and training you set up next.'\n )\n : step === 2\n ? tr(\n 'atheletebridge.pages.onboarding.step2Subtitle',\n 'You can add players and fixtures once your team exists.'\n )\n : tr(\n 'atheletebridge.pages.onboarding.step3Subtitle',\n 'We add you to this squad so the roster starts with you.'\n );\n\n return (\n <div className=\"min-h-full bg-bg-canvas px-4 py-10 sm:px-6 sm:py-14\">\n <div className=\"mx-auto flex w-full max-w-2xl flex-col gap-8\">\n <Stepper current={step} />\n\n <header className=\"text-center\">\n <h1 className=\"text-3xl font-bold tracking-tight text-text-primary sm:text-4xl\">\n {heading}\n </h1>\n <p className=\"mx-auto mt-2 max-w-lg text-sm text-text-secondary sm:text-base\">\n {subheading}\n </p>\n </header>\n\n <form onSubmit={handleFinish} noValidate>\n <GlassCard treatment=\"insight\" className=\"p-6 sm:p-8\">\n <div className=\"space-y-6\">\n {step === 1 && <SportChips value={sport} onSelect={handleSelectSport} size=\"lg\" />}\n\n {step === 2 && (\n <>\n <FormField\n label={tr('atheletebridge.pages.onboarding.teamName', 'Team name')}\n htmlFor=\"onboarding-team-name\"\n required\n >\n <input\n id=\"onboarding-team-name\"\n className={fieldInputClass}\n value={teamName}\n maxLength={TEAM_NAME_LIMIT}\n onChange={(e) => {\n setTeamName(e.target.value);\n setError(null);\n }}\n placeholder=\"e.g. Riverside United U16\"\n />\n </FormField>\n\n <FormField label={tr('atheletebridge.pages.onboarding.sport', 'Sport')}>\n <SportChips value={sport} onSelect={handleSelectSport} size=\"sm\" />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField\n label={tr('atheletebridge.pages.onboarding.ageGroup', 'Age group')}\n htmlFor=\"onboarding-age-group\"\n >\n <select\n id=\"onboarding-age-group\"\n className={fieldInputClass}\n value={ageGroup}\n onChange={(e) => setAgeGroup(e.target.value)}\n >\n {AGE_GROUPS.map((option) => (\n <option key={option.value || 'none'} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField\n label={tr('atheletebridge.pages.onboarding.season', 'Season')}\n htmlFor=\"onboarding-season\"\n >\n <select\n id=\"onboarding-season\"\n className={fieldInputClass}\n value={season}\n onChange={(e) => setSeason(e.target.value)}\n >\n {seasonOptions.map((option) => (\n <option key={option.value || 'none'} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n </>\n )}\n\n {step === 3 && (\n <div className=\"flex flex-wrap gap-2\" role=\"group\" aria-label=\"Your role\">\n {MEMBER_ROLE_OPTIONS.map((option) => {\n const active = role === option;\n return (\n <button\n key={option}\n type=\"button\"\n onClick={() => {\n setRole(option);\n setError(null);\n }}\n aria-pressed={active}\n className={cn(\n CHIP_BASE,\n 'px-5 py-2.5 text-base',\n active ? CHIP_ACTIVE : CHIP_IDLE\n )}\n >\n <Users className=\"h-5 w-5\" aria-hidden=\"true\" />\n {MEMBER_ROLE_LABELS[option]}\n </button>\n );\n })}\n </div>\n )}\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error}\n </p>\n )}\n </div>\n\n <div className=\"mt-8 flex flex-wrap items-center justify-between gap-3 border-t border-border-seam pt-6\">\n <button\n type=\"button\"\n onClick={step === 1 ? goDashboard : handleBack}\n className={SECONDARY_BUTTON}\n disabled={busy}\n >\n <ArrowLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n {step === 1\n ? tr('atheletebridge.pages.onboarding.exit', 'Exit')\n : tr('atheletebridge.pages.onboarding.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goDashboard}\n className={GHOST_BUTTON}\n disabled={busy}\n >\n {tr('atheletebridge.pages.onboarding.skip', 'Skip for now')}\n </button>\n\n {step === 3 ? (\n <button type=\"submit\" className={PRIMARY_BUTTON} disabled={busy || !role}>\n {busy ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {tr('atheletebridge.pages.onboarding.creating', 'Creating…')}\n </>\n ) : (\n <>\n {tr('atheletebridge.pages.onboarding.finish', 'Create team')}\n <ArrowRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </>\n )}\n </button>\n ) : (\n <button type=\"button\" onClick={handleContinue} className={PRIMARY_BUTTON}>\n {tr('atheletebridge.pages.onboarding.continue', 'Continue')}\n <ArrowRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n </form>\n\n <p className=\"text-center text-sm text-text-secondary\">\n {tr('atheletebridge.pages.onboarding.footer', 'Step')} {step}{' '}\n {tr('atheletebridge.pages.onboarding.footerOf', 'of')} {TOTAL_STEPS} —{' '}\n {tr('atheletebridge.pages.onboarding.footerHint', 'takes about a minute.')}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OnboardingPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/onboarding/OnboardingPage.tsx"],"sourcesContent":["/**\n * First-run onboarding wizard\n * @module atheletebridge/pages/onboarding\n *\n * The AthleteBridge first-run flow: three steps — your sport → create your team\n * → your role — landing the user on a usable team page. Design reference is\n * mockup 26 (`atheletebridge-specs/product/mockups`), realised entirely with\n * fe-libs semantic tokens (the mockup's orange→red brand ramp maps to\n * `--color-action-primary-bg` blended toward `--color-accent-purple`, exactly as\n * the dashboard hero CTA does; the mockup's teal-green \"complete\" maps to\n * `--color-accent-teal`).\n *\n * Scope is deliberately narrow — sport, team, role. Workspace/organization\n * creation is NOT part of this flow: `global-tenant-svc` owns it and it already\n * precedes product entry, so duplicating it here would fork a platform module\n * (see `workspaces-specs/business/strategy/module-ownership`).\n *\n * Data layer (all real, nothing mocked):\n * - `useCreateTeam` → `athCreateTeam` — creates the squad from step 2.\n * - `useAddMember` → `athAddMember` — puts the calling user on that squad's\n * roster with the role picked in step 3. Best-effort: the team is the primary\n * outcome and is already persisted, so a roster hiccup must not strand the\n * user mid-flow.\n *\n * Unlike the entity pages this is a calm, centered flow rather than the admin\n * `PageLayout` header, and it renders no product wordmark — the app shell chrome\n * already carries AthleteBridge branding above this surface.\n */\n\nimport { useCallback, useMemo, useState, type FC, type FormEvent } from 'react';\nimport { ArrowLeft, ArrowRight, Check, Loader2, Users } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\n\nimport { FormField, fieldInputClass } from '../../components/form';\nimport {\n MEMBER_ROLE_LABELS,\n MEMBER_ROLE_OPTIONS,\n SPORT_ICONS,\n SPORT_LABELS,\n SPORT_OPTIONS,\n} from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAddMember, useCreateTeam } from '../../hooks/useAthleteBridgeApi';\nimport type { AthMemberRole, AthSport } from '../../types';\nimport { cn } from '../../utils/cn';\nimport { useAbTranslate } from '../../i18n';\n\n// ============================================================================\n// Static option catalogues\n// ============================================================================\n\n/**\n * `AthTeam.ageGroup` is a free-text column on the subgraph; these are the\n * conventional squad bands offered as a shortcut. \"Senior\" and \"Masters\" cover\n * non-age-banded squads, and the empty value leaves the column unset.\n */\nconst AGE_GROUPS: ReadonlyArray<{ value: string; label: string }> = [\n { value: '', label: 'Not age-banded' },\n { value: 'U8', label: 'U8' },\n { value: 'U10', label: 'U10' },\n { value: 'U12', label: 'U12' },\n { value: 'U14', label: 'U14' },\n { value: 'U16', label: 'U16' },\n { value: 'U18', label: 'U18' },\n { value: 'U21', label: 'U21' },\n { value: 'SENIOR', label: 'Senior' },\n { value: 'MASTERS', label: 'Masters' },\n];\n\n/**\n * `AthTeam.season` is also free text. Options are derived from the current date\n * rather than hard-coded, so the list never goes stale: the calendar year, the\n * split-year season either side of it, and the previous calendar year.\n */\nfunction buildSeasonOptions(now: Date): ReadonlyArray<{ value: string; label: string }> {\n const year = now.getFullYear();\n const short = (y: number): string => String(y % 100).padStart(2, '0');\n return [\n { value: '', label: 'No season' },\n { value: String(year), label: String(year) },\n { value: `${year}/${short(year + 1)}`, label: `${year}/${short(year + 1)}` },\n { value: `${year - 1}/${short(year)}`, label: `${year - 1}/${short(year)}` },\n { value: String(year - 1), label: String(year - 1) },\n ];\n}\n\nconst TOTAL_STEPS = 3;\nconst TEAM_NAME_LIMIT = 120;\n\ntype StepNumber = 1 | 2 | 3;\n\nconst STEP_LABELS: Record<StepNumber, string> = {\n 1: 'Your sport',\n 2: 'Create your team',\n 3: 'Your role',\n};\n\n// ============================================================================\n// Shared token-only styling\n// ============================================================================\n\n/**\n * Brand gradient — the same orange→purple ramp the dashboard hero CTA uses, so\n * the wizard's active step and primary action read as one brand with the rest of\n * the product. Token-only (no raw colors).\n */\nconst BRAND_GRADIENT =\n 'bg-[linear-gradient(120deg,var(--color-action-primary-bg),color-mix(in_oklab,var(--color-action-primary-bg)_62%,var(--color-accent-purple)),var(--color-action-primary-bg))]';\n\nconst PRIMARY_BUTTON = cn(\n 'inline-flex items-center justify-center gap-1.5 rounded-lg px-5 py-2.5 text-sm font-semibold',\n 'text-action-primary-text shadow-elevation-1 transition-[filter,box-shadow]',\n 'hover:shadow-elevation-2 hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-60',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n BRAND_GRADIENT\n);\n\nconst SECONDARY_BUTTON = cn(\n 'inline-flex items-center justify-center gap-1.5 rounded-lg border border-border-seam px-5 py-2.5',\n 'text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n);\n\nconst GHOST_BUTTON = cn(\n 'rounded-lg px-4 py-2.5 text-sm font-medium text-text-secondary transition-colors',\n 'hover:text-text-primary focus-visible:outline-none focus-visible:ring-2',\n 'focus-visible:ring-[var(--color-focus-ring)]'\n);\n\nconst CHIP_BASE = cn(\n 'inline-flex items-center gap-2 rounded-full border px-4 py-2 text-sm font-medium transition-all',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n 'motion-safe:hover:-translate-y-0.5'\n);\n\nconst CHIP_IDLE =\n 'border-border-seam bg-bg-surface text-text-secondary hover:border-border-strong hover:text-text-primary';\n\nconst CHIP_ACTIVE = cn(\n 'border-transparent text-action-primary-text shadow-elevation-1',\n BRAND_GRADIENT\n);\n\n// ============================================================================\n// Stepper\n// ============================================================================\n\ninterface StepperProps {\n current: StepNumber;\n}\n\n/**\n * The mockup's three-node stepper: teal-green + tick for a completed step, brand\n * gradient for the active step, hollow outline for an upcoming one. The nodes are\n * decorative — the accessible state is carried by the `progressbar` wrapper and\n * the visible step caption below the card.\n */\nconst Stepper: FC<StepperProps> = ({ current }) => (\n <div\n className=\"flex items-start justify-center\"\n role=\"progressbar\"\n aria-valuemin={1}\n aria-valuemax={TOTAL_STEPS}\n aria-valuenow={current}\n aria-valuetext={`Step ${current} of ${TOTAL_STEPS}: ${STEP_LABELS[current]}`}\n aria-label=\"Onboarding progress\"\n >\n {([1, 2, 3] as StepNumber[]).map((step, index) => {\n const done = step < current;\n const active = step === current;\n return (\n <div key={step} className=\"flex items-start\" aria-hidden=\"true\">\n {index > 0 && (\n <span\n className={cn(\n 'mt-6 h-px w-10 shrink-0 transition-colors sm:w-20',\n step <= current ? 'bg-[var(--color-accent-teal)]' : 'bg-border-seam'\n )}\n />\n )}\n <div className=\"flex w-24 flex-col items-center gap-2 sm:w-32\">\n <span\n className={cn(\n 'flex h-12 w-12 items-center justify-center rounded-full text-base font-semibold transition-all',\n done && 'bg-[var(--color-accent-teal)] text-[var(--color-bg-canvas)]',\n active && cn('text-action-primary-text shadow-elevation-2', BRAND_GRADIENT),\n !done && !active && 'border border-border-strong text-text-muted'\n )}\n >\n {done ? <Check className=\"h-5 w-5\" /> : step}\n </span>\n <span\n className={cn(\n 'text-center text-sm',\n active ? 'font-semibold text-text-primary' : 'text-text-secondary'\n )}\n >\n {STEP_LABELS[step]}\n </span>\n </div>\n </div>\n );\n })}\n </div>\n);\n\n// ============================================================================\n// Sport chips\n// ============================================================================\n\ninterface SportChipsProps {\n value: AthSport | null;\n onSelect: (sport: AthSport) => void;\n size: 'lg' | 'sm';\n}\n\nconst SportChips: FC<SportChipsProps> = ({ value, onSelect, size }) => (\n <div className=\"flex flex-wrap gap-2\" role=\"group\" aria-label=\"Sport\">\n {SPORT_OPTIONS.map((sport) => {\n const Icon = SPORT_ICONS[sport];\n const active = value === sport;\n return (\n <button\n key={sport}\n type=\"button\"\n onClick={() => onSelect(sport)}\n aria-pressed={active}\n className={cn(\n CHIP_BASE,\n size === 'lg' && 'px-5 py-2.5 text-base',\n active ? CHIP_ACTIVE : CHIP_IDLE\n )}\n >\n <Icon className={size === 'lg' ? 'h-5 w-5' : 'h-4 w-4'} aria-hidden=\"true\" />\n {SPORT_LABELS[sport]}\n </button>\n );\n })}\n </div>\n);\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport const OnboardingPage: FC = () => {\n const tr = useAbTranslate();\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const createTeam = useCreateTeam();\n const addMember = useAddMember();\n\n const [step, setStep] = useState<StepNumber>(1);\n const [sport, setSport] = useState<AthSport | null>(null);\n const [teamName, setTeamName] = useState('');\n const [ageGroup, setAgeGroup] = useState('');\n const [season, setSeason] = useState('');\n const [role, setRole] = useState<AthMemberRole | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const seasonOptions = useMemo(() => buildSeasonOptions(new Date()), []);\n\n const busy = createTeam.isPending || addMember.isPending;\n\n const goDashboard = useCallback(() => navigateTo('/'), [navigateTo]);\n\n const handleBack = useCallback(() => {\n setError(null);\n setStep((prev) => (prev === 1 ? 1 : ((prev - 1) as StepNumber)));\n }, []);\n\n const handleSelectSport = useCallback((next: AthSport) => {\n setSport(next);\n setError(null);\n }, []);\n\n /**\n * Step 1/2 advance. Step 3 submits, so this only guards the two picker steps.\n */\n const handleContinue = useCallback(() => {\n if (step === 1) {\n if (!sport) {\n setError(tr('atheletebridge.pages.onboarding.errorSport', 'Pick a sport to continue.'));\n return;\n }\n setError(null);\n setStep(2);\n return;\n }\n if (!teamName.trim()) {\n setError(\n tr('atheletebridge.pages.onboarding.errorTeamName', 'Give your team a name to continue.')\n );\n return;\n }\n setError(null);\n setStep(3);\n }, [step, sport, teamName, tr]);\n\n /**\n * Final submit — creates the team, then (best-effort) puts the calling user on\n * its roster with the chosen role, and lands them on the new team page.\n */\n const handleFinish = useCallback(\n async (event: FormEvent) => {\n event.preventDefault();\n if (!sport || !role || !teamName.trim()) {\n setError(tr('atheletebridge.pages.onboarding.errorRole', 'Pick your role to finish.'));\n return;\n }\n setError(null);\n\n try {\n const team = await createTeam.mutateAsync({\n name: teamName.trim(),\n sport,\n status: 'FORMING',\n ageGroup: ageGroup || undefined,\n season: season || undefined,\n });\n\n if (currentUser?.id) {\n const fullName = [currentUser.firstName, currentUser.lastName]\n .filter((part): part is string => Boolean(part))\n .join(' ')\n .trim();\n const displayName = currentUser.name ?? (fullName || currentUser.email);\n try {\n await addMember.mutateAsync({\n teamId: team.id,\n userId: currentUser.id,\n displayName,\n role,\n status: 'ACTIVE',\n });\n } catch {\n // The team is created and is the primary outcome — a roster failure\n // must not strand the user in the wizard. They can add themselves\n // from the team page.\n }\n }\n\n navigateTo(`/teams/${team.id}`);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n 'atheletebridge.pages.onboarding.errorCreate',\n \"We couldn't create your team. Please try again.\"\n )\n );\n }\n },\n [sport, role, teamName, ageGroup, season, currentUser, createTeam, addMember, navigateTo, tr]\n );\n\n const heading =\n step === 1\n ? tr('atheletebridge.pages.onboarding.step1Title', 'What do you coach?')\n : step === 2\n ? tr('atheletebridge.pages.onboarding.step2Title', 'Create your first team')\n : tr('atheletebridge.pages.onboarding.step3Title', \"What's your role?\");\n\n const subheading =\n step === 1\n ? tr(\n 'atheletebridge.pages.onboarding.step1Subtitle',\n 'Your sport shapes the squads, fixtures and training you set up next.'\n )\n : step === 2\n ? tr(\n 'atheletebridge.pages.onboarding.step2Subtitle',\n 'You can add players and fixtures once your team exists.'\n )\n : tr(\n 'atheletebridge.pages.onboarding.step3Subtitle',\n 'We add you to this squad so the roster starts with you.'\n );\n\n return (\n <div className=\"min-h-full bg-bg-canvas px-4 py-10 sm:px-6 sm:py-14\">\n <div className=\"mx-auto flex w-full max-w-2xl flex-col gap-8\">\n <Stepper current={step} />\n\n <header className=\"text-center\">\n <h1 className=\"text-3xl font-bold tracking-tight text-text-primary sm:text-4xl\">\n {heading}\n </h1>\n <p className=\"mx-auto mt-2 max-w-lg text-sm text-text-secondary sm:text-base\">\n {subheading}\n </p>\n </header>\n\n <form onSubmit={handleFinish} noValidate>\n <GlassCard treatment=\"insight\" className=\"p-6 sm:p-8\">\n <div className=\"space-y-6\">\n {step === 1 && <SportChips value={sport} onSelect={handleSelectSport} size=\"lg\" />}\n\n {step === 2 && (\n <>\n <FormField\n label={tr('atheletebridge.pages.onboarding.teamName', 'Team name')}\n htmlFor=\"onboarding-team-name\"\n required\n >\n <input\n id=\"onboarding-team-name\"\n className={fieldInputClass}\n value={teamName}\n maxLength={TEAM_NAME_LIMIT}\n onChange={(e) => {\n setTeamName(e.target.value);\n setError(null);\n }}\n placeholder=\"e.g. Riverside United U16\"\n />\n </FormField>\n\n <FormField label={tr('atheletebridge.pages.onboarding.sport', 'Sport')}>\n <SportChips value={sport} onSelect={handleSelectSport} size=\"sm\" />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField\n label={tr('atheletebridge.pages.onboarding.ageGroup', 'Age group')}\n htmlFor=\"onboarding-age-group\"\n >\n <select\n id=\"onboarding-age-group\"\n className={fieldInputClass}\n value={ageGroup}\n onChange={(e) => setAgeGroup(e.target.value)}\n >\n {AGE_GROUPS.map((option) => (\n <option key={option.value || 'none'} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField\n label={tr('atheletebridge.pages.onboarding.season', 'Season')}\n htmlFor=\"onboarding-season\"\n >\n <select\n id=\"onboarding-season\"\n className={fieldInputClass}\n value={season}\n onChange={(e) => setSeason(e.target.value)}\n >\n {seasonOptions.map((option) => (\n <option key={option.value || 'none'} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n </>\n )}\n\n {step === 3 && (\n <div className=\"flex flex-wrap gap-2\" role=\"group\" aria-label=\"Your role\">\n {MEMBER_ROLE_OPTIONS.map((option) => {\n const active = role === option;\n return (\n <button\n key={option}\n type=\"button\"\n onClick={() => {\n setRole(option);\n setError(null);\n }}\n aria-pressed={active}\n className={cn(\n CHIP_BASE,\n 'px-5 py-2.5 text-base',\n active ? CHIP_ACTIVE : CHIP_IDLE\n )}\n >\n <Users className=\"h-5 w-5\" aria-hidden=\"true\" />\n {MEMBER_ROLE_LABELS[option]}\n </button>\n );\n })}\n </div>\n )}\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error}\n </p>\n )}\n </div>\n\n <div className=\"mt-8 flex flex-wrap items-center justify-between gap-3 border-t border-border-seam pt-6\">\n <button\n type=\"button\"\n onClick={step === 1 ? goDashboard : handleBack}\n className={SECONDARY_BUTTON}\n disabled={busy}\n >\n <ArrowLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n {step === 1\n ? tr('atheletebridge.pages.onboarding.exit', 'Exit')\n : tr('atheletebridge.pages.onboarding.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goDashboard}\n className={GHOST_BUTTON}\n disabled={busy}\n >\n {tr('atheletebridge.pages.onboarding.skip', 'Skip for now')}\n </button>\n\n {step === 3 ? (\n <button type=\"submit\" className={PRIMARY_BUTTON} disabled={busy || !role}>\n {busy ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n {tr('atheletebridge.pages.onboarding.creating', 'Creating…')}\n </>\n ) : (\n <>\n {tr('atheletebridge.pages.onboarding.finish', 'Create team')}\n <ArrowRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </>\n )}\n </button>\n ) : (\n <button type=\"button\" onClick={handleContinue} className={PRIMARY_BUTTON}>\n {tr('atheletebridge.pages.onboarding.continue', 'Continue')}\n <ArrowRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n </form>\n\n <p className=\"text-center text-sm text-text-secondary\">\n {tr('atheletebridge.pages.onboarding.footer', 'Step')} {step}{' '}\n {tr('atheletebridge.pages.onboarding.footerOf', 'of')} {TOTAL_STEPS} —{' '}\n {tr('atheletebridge.pages.onboarding.footerHint', 'takes about a minute.')}\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|