@burdenoff/microfe-atheletebridge 2026.1031.2 → 2026.1102.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.
Files changed (75) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoot.d.ts.map +1 -1
  2. package/dist/atheletebridge/AthleteBridgeRoot.js +63 -47
  3. package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
  4. package/dist/atheletebridge/AthleteBridgeRoutes.d.ts.map +1 -1
  5. package/dist/atheletebridge/AthleteBridgeRoutes.js +62 -6
  6. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  7. package/dist/atheletebridge/components/StatusBadge.d.ts.map +1 -1
  8. package/dist/atheletebridge/components/StatusBadge.js +13 -1
  9. package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
  10. package/dist/atheletebridge/components/registration/PaymentSummaryPanel.d.ts +6 -0
  11. package/dist/atheletebridge/components/registration/PaymentSummaryPanel.d.ts.map +1 -0
  12. package/dist/atheletebridge/components/registration/PaymentSummaryPanel.js +104 -0
  13. package/dist/atheletebridge/components/registration/PaymentSummaryPanel.js.map +1 -0
  14. package/dist/atheletebridge/components/registration/RegistrationEntryFields.d.ts +15 -0
  15. package/dist/atheletebridge/components/registration/RegistrationEntryFields.d.ts.map +1 -0
  16. package/dist/atheletebridge/components/registration/RegistrationEntryFields.js +236 -0
  17. package/dist/atheletebridge/components/registration/RegistrationEntryFields.js.map +1 -0
  18. package/dist/atheletebridge/components/registration/entryRules.d.ts +23 -0
  19. package/dist/atheletebridge/components/registration/entryRules.d.ts.map +1 -0
  20. package/dist/atheletebridge/components/registration/entryRules.js +29 -0
  21. package/dist/atheletebridge/components/registration/entryRules.js.map +1 -0
  22. package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +6 -2
  23. package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
  24. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +297 -281
  25. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  26. package/dist/atheletebridge/hooks/useRegistrationApi.d.ts +54 -0
  27. package/dist/atheletebridge/hooks/useRegistrationApi.d.ts.map +1 -0
  28. package/dist/atheletebridge/hooks/useRegistrationApi.js +265 -0
  29. package/dist/atheletebridge/hooks/useRegistrationApi.js.map +1 -0
  30. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
  31. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +123 -416
  32. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  33. package/dist/atheletebridge/pages/registrations/MyRegistrationsPage.d.ts +10 -0
  34. package/dist/atheletebridge/pages/registrations/MyRegistrationsPage.d.ts.map +1 -0
  35. package/dist/atheletebridge/pages/registrations/MyRegistrationsPage.js +89 -0
  36. package/dist/atheletebridge/pages/registrations/MyRegistrationsPage.js.map +1 -0
  37. package/dist/atheletebridge/pages/registrations/RecordRegistrationPage.d.ts +10 -0
  38. package/dist/atheletebridge/pages/registrations/RecordRegistrationPage.d.ts.map +1 -0
  39. package/dist/atheletebridge/pages/registrations/RecordRegistrationPage.js +94 -0
  40. package/dist/atheletebridge/pages/registrations/RecordRegistrationPage.js.map +1 -0
  41. package/dist/atheletebridge/pages/registrations/RegisterPage.d.ts +11 -0
  42. package/dist/atheletebridge/pages/registrations/RegisterPage.d.ts.map +1 -0
  43. package/dist/atheletebridge/pages/registrations/RegisterPage.js +160 -0
  44. package/dist/atheletebridge/pages/registrations/RegisterPage.js.map +1 -0
  45. package/dist/atheletebridge/pages/registrations/RegistrationFormBuilderPage.d.ts +10 -0
  46. package/dist/atheletebridge/pages/registrations/RegistrationFormBuilderPage.d.ts.map +1 -0
  47. package/dist/atheletebridge/pages/registrations/RegistrationFormBuilderPage.js +602 -0
  48. package/dist/atheletebridge/pages/registrations/RegistrationFormBuilderPage.js.map +1 -0
  49. package/dist/atheletebridge/pages/registrations/RegistrationFormDetailPage.d.ts +9 -0
  50. package/dist/atheletebridge/pages/registrations/RegistrationFormDetailPage.d.ts.map +1 -0
  51. package/dist/atheletebridge/pages/registrations/RegistrationFormDetailPage.js +317 -0
  52. package/dist/atheletebridge/pages/registrations/RegistrationFormDetailPage.js.map +1 -0
  53. package/dist/atheletebridge/pages/registrations/RegistrationFormsListPage.d.ts +9 -0
  54. package/dist/atheletebridge/pages/registrations/RegistrationFormsListPage.d.ts.map +1 -0
  55. package/dist/atheletebridge/pages/registrations/RegistrationFormsListPage.js +127 -0
  56. package/dist/atheletebridge/pages/registrations/RegistrationFormsListPage.js.map +1 -0
  57. package/dist/atheletebridge/pages/registrations/RegistrationSubmissionPage.d.ts +11 -0
  58. package/dist/atheletebridge/pages/registrations/RegistrationSubmissionPage.d.ts.map +1 -0
  59. package/dist/atheletebridge/pages/registrations/RegistrationSubmissionPage.js +411 -0
  60. package/dist/atheletebridge/pages/registrations/RegistrationSubmissionPage.js.map +1 -0
  61. package/dist/atheletebridge/types/registration.d.ts +220 -0
  62. package/dist/atheletebridge/types/registration.d.ts.map +1 -0
  63. package/dist/atheletebridge/utils/fixtureCapabilities.d.ts +19 -0
  64. package/dist/atheletebridge/utils/fixtureCapabilities.d.ts.map +1 -0
  65. package/dist/atheletebridge/utils/fixtureCapabilities.js +21 -0
  66. package/dist/atheletebridge/utils/fixtureCapabilities.js.map +1 -0
  67. package/dist/atheletebridge/utils/money.d.ts +22 -0
  68. package/dist/atheletebridge/utils/money.d.ts.map +1 -0
  69. package/dist/atheletebridge/utils/money.js +38 -0
  70. package/dist/atheletebridge/utils/money.js.map +1 -0
  71. package/dist/atheletebridge/utils/registration.d.ts +17 -0
  72. package/dist/atheletebridge/utils/registration.d.ts.map +1 -0
  73. package/dist/atheletebridge/utils/registration.js +25 -0
  74. package/dist/atheletebridge/utils/registration.js.map +1 -0
  75. package/package.json +1 -1
@@ -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';\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 { useScheduledTimeGate } from '../../hooks';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\nimport {\n buildCricketResultInput,\n buildRecordResultInput,\n parseScoreInput,\n} from '../../utils/recordResult';\nimport { useAbTranslate } from '../../i18n';\n\nexport function FixtureDetailPage() {\n const tr = useAbTranslate();\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 // Knockout outcome (BOFF-7640) — only offered when the fixture is level at\n // full time; a tie still level after extra time must record the shootout.\n const [extraTimeHome, setExtraTimeHome] = useState('');\n const [extraTimeAway, setExtraTimeAway] = useState('');\n const [penaltyHome, setPenaltyHome] = useState('');\n const [penaltyAway, setPenaltyAway] = useState('');\n // Cricket scorecard form (BOFF-7644) — replaces the generic score inputs\n // when the fixture's home team plays cricket.\n const [cricketForm, setCricketForm] = useState({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME' as 'HOME' | 'AWAY',\n decidedBy: 'RUNS' as 'RUNS' | 'WICKETS',\n margin: '',\n superOver: false,\n dls: false,\n });\n const [error, setError] = useState<string | null>(null);\n // Time gate for the completion action; re-renders when kickoff arrives.\n const fixtureStarted = useScheduledTimeGate(fixture.data?.scheduledAt);\n\n const setCricket = <K extends keyof typeof cricketForm>(\n key: K,\n value: (typeof cricketForm)[K]\n ): void => setCricketForm((prev) => ({ ...prev, [key]: value }));\n\n const resetCricketForm = (): void =>\n setCricketForm({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME',\n decidedBy: 'RUNS',\n margin: '',\n superOver: false,\n dls: false,\n });\n\n if (fixture.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <LoadingState\n label={tr('atheletebridge.pages.fixtureDetail.loading', 'Loading fixture…')}\n />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <ErrorState\n title={tr('atheletebridge.pages.fixtureDetail.notFoundTitle', 'Fixture not found')}\n message={tr(\n 'atheletebridge.pages.fixtureDetail.notFoundMessage',\n 'This fixture does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? tr('atheletebridge.pages.fixtureDetail.home', 'Home');\n const awayName =\n f.awayTeam?.name ??\n (f.awayTeamId ? tr('atheletebridge.pages.fixtureDetail.away', 'Away') : 'TBD');\n // Result entry is the completion transition, gated on kickoff here and\n // enforced server-side too. Both branches share isResultEligible so they\n // cannot drift apart.\n const isResultEligible =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n const canRecordResult = isResultEligible && fixtureStarted;\n const awaitingKickoff = isResultEligible && !fixtureStarted;\n\n const isCricket = f.homeTeam?.sport === 'CRICKET';\n\n // Knockout-outcome inputs (BOFF-7640) — offered once the full-time score is\n // entered and level. The shootout appears while the tie is still level\n // (straight from full time, or after a level extra time).\n const parsedHomeScore = parseScoreInput(homeScore);\n const parsedAwayScore = parseScoreInput(awayScore);\n const fullTimeScoresEntered = parsedHomeScore !== null && parsedAwayScore !== null;\n const levelAtFullTime = fullTimeScoresEntered && parsedHomeScore === parsedAwayScore;\n const extraTimeEntered = extraTimeHome !== '' || extraTimeAway !== '';\n const extraTimeParsedHome = parseScoreInput(extraTimeHome);\n const extraTimeParsedAway = parseScoreInput(extraTimeAway);\n const extraTimeComplete = extraTimeParsedHome !== null && extraTimeParsedAway !== null;\n const stillLevelAfterExtraTime = extraTimeComplete && extraTimeParsedHome === extraTimeParsedAway;\n const showPenaltyShootout = levelAtFullTime && (!extraTimeEntered || stillLevelAfterExtraTime);\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n\n if (isCricket) {\n const draft = buildCricketResultInput(\n { ...cricketForm },\n {\n required: tr(\n 'atheletebridge.pages.fixtureDetail.cricketRequired',\n 'All innings scores, overs and the margin are required.'\n ),\n negative: tr(\n 'atheletebridge.pages.fixtureDetail.cricketNegative',\n 'Scores and overs cannot be negative.'\n ),\n wicketsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketsRange',\n 'Wickets must be between 0 and 10.'\n ),\n ballsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketBallsRange',\n 'Balls must be between 0 and 5.'\n ),\n marginMinimum: tr(\n 'atheletebridge.pages.fixtureDetail.cricketMarginMinimum',\n 'The margin must be at least 1.'\n ),\n wicketMarginMax: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketMarginMax',\n 'A win by wickets cannot exceed a margin of 10.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n resetCricketForm();\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\n }\n return;\n }\n\n // Non-cricket fixtures: a single pair of scores, optionally followed by a\n // knockout outcome. All the rules live in `buildRecordResultInput` so the\n // page only renders copy and submits the payload it hands back.\n const draft = buildRecordResultInput(\n { homeScore, awayScore, extraTimeHome, extraTimeAway, penaltyHome, penaltyAway },\n {\n scoresRequired: tr(\n 'atheletebridge.pages.fixtureDetail.scoresRequired',\n 'Both scores are required and must be numbers.'\n ),\n scoresNegative: tr(\n 'atheletebridge.pages.fixtureDetail.scoresNegative',\n 'Scores cannot be negative.'\n ),\n extraTimePair: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimePair',\n 'Enter both extra-time scores.'\n ),\n extraTimeNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNumeric',\n 'Extra-time scores must be numbers.'\n ),\n extraTimeNegative: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNegative',\n 'Extra-time scores cannot be negative.'\n ),\n penaltyPair: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyPair',\n 'Enter both penalty scores.'\n ),\n penaltyNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNumeric',\n 'Penalty scores must be numbers.'\n ),\n penaltyNegative: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNegative',\n 'Penalty scores cannot be negative.'\n ),\n extraTimeDecided: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeDecided',\n 'Extra time decided the tie — leave the penalty shootout empty.'\n ),\n extraTimeLevel: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeLevel',\n 'Still level after extra time — record the penalty shootout.'\n ),\n penaltyLevel: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyLevel',\n 'Penalty scores must have a winner.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n setHomeScore('');\n setAwayScore('');\n setExtraTimeHome('');\n setExtraTimeAway('');\n setPenaltyHome('');\n setPenaltyAway('');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\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\" /> {tr('atheletebridge.pages.fixtureDetail.back', '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\">\n {tr('atheletebridge.pages.fixtureDetail.notes', 'Notes')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {awaitingKickoff && (\n <EmphasisPanel className=\"mb-8\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultUnavailable',\n 'A result can be recorded once this fixture has started — after its scheduled date and time.'\n )}\n </p>\n </EmphasisPanel>\n )}\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\" />{' '}\n {tr('atheletebridge.pages.fixtureDetail.recordResult', '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 {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultDescription',\n 'Recording a final score marks this fixture Completed and recomputes the competition standings for both teams.'\n )}\n </p>\n <form onSubmit={handleRecord}>\n {isCricket ? (\n <div className=\"flex flex-col items-center gap-5\">\n <p className=\"text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.cricketScorecardDescription',\n 'Record each innings score and how the match was decided — a cricket result is a margin, not a score comparison.'\n )}\n </p>\n\n {(\n [\n { side: 'home' as const, name: homeName, sport: f.homeTeam?.sport },\n { side: 'away' as const, name: awayName, sport: f.awayTeam?.sport },\n ] as const\n ).map(({ side, name, sport }) => (\n <div key={side} className=\"flex flex-wrap items-end justify-center gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={name} sport={sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {name}\n </span>\n </div>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n htmlFor={`cricket-${side}-runs`}\n className=\"w-20\"\n >\n <input\n id={`cricket-${side}-runs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Runs`]}\n onChange={(e) => setCricket(`${side}Runs`, e.target.value)}\n placeholder=\"185\"\n />\n </FormField>\n <span className=\"pb-3 text-lg font-bold text-text-muted\">/</span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n htmlFor={`cricket-${side}-wickets`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-wickets`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Wickets`]}\n onChange={(e) => setCricket(`${side}Wickets`, e.target.value)}\n placeholder=\"6\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.overs', 'Overs')}\n htmlFor={`cricket-${side}-overs`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-overs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Overs`]}\n onChange={(e) => setCricket(`${side}Overs`, e.target.value)}\n placeholder=\"20\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.balls', 'Balls')}\n htmlFor={`cricket-${side}-balls`}\n className=\"w-14\"\n >\n <input\n id={`cricket-${side}-balls`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Balls`]}\n onChange={(e) => setCricket(`${side}Balls`, e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n ))}\n\n <div className=\"flex flex-wrap items-end justify-center gap-4\">\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.winner', 'Winner')}\n htmlFor=\"cricket-winner\"\n >\n <select\n id=\"cricket-winner\"\n className={fieldInputClass}\n value={cricketForm.winner}\n onChange={(e) => setCricket('winner', e.target.value as 'HOME' | 'AWAY')}\n >\n <option value=\"HOME\">{homeName}</option>\n <option value=\"AWAY\">{awayName}</option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.decidedBy', 'Decided by')}\n htmlFor=\"cricket-decided-by\"\n >\n <select\n id=\"cricket-decided-by\"\n className={fieldInputClass}\n value={cricketForm.decidedBy}\n onChange={(e) =>\n setCricket('decidedBy', e.target.value as 'RUNS' | 'WICKETS')\n }\n >\n <option value=\"RUNS\">\n {tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n </option>\n <option value=\"WICKETS\">\n {tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n </option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.margin', 'Margin')}\n htmlFor=\"cricket-margin\"\n className=\"w-20\"\n >\n <input\n id=\"cricket-margin\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm.margin}\n onChange={(e) => setCricket('margin', e.target.value)}\n placeholder=\"13\"\n />\n </FormField>\n </div>\n\n <div className=\"flex items-center gap-6\">\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.superOver}\n onChange={(e) => setCricket('superOver', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.superOver', 'Super Over')}\n </label>\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.dls}\n onChange={(e) => setCricket('dls', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.dls', 'DLS')}\n </label>\n </div>\n </div>\n ) : (\n <>\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\n label={tr('atheletebridge.pages.fixtureDetail.home', 'Home')}\n htmlFor=\"homeScore\"\n className=\"w-24\"\n >\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\n label={tr('atheletebridge.pages.fixtureDetail.away', 'Away')}\n htmlFor=\"awayScore\"\n className=\"w-24\"\n >\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 {levelAtFullTime && (\n <div className=\"mt-6 border-t border-border-seam pt-5\">\n <p className=\"mb-4 text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.levelAtFullTime',\n 'Level at full time — record how the tie was decided (optional).'\n )}\n </p>\n <div className=\"flex flex-col items-center gap-4\">\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.extraTime', 'Extra time')}\n htmlFor=\"extraTimeHome\"\n className=\"w-20\"\n >\n <input\n id=\"extraTimeHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeHome}\n onChange={(e) => setExtraTimeHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"extraTimeAway\" className=\"w-20\">\n <input\n id=\"extraTimeAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeAway}\n onChange={(e) => setExtraTimeAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n\n {showPenaltyShootout && (\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr(\n 'atheletebridge.pages.fixtureDetail.penaltyShootout',\n 'Penalties'\n )}\n htmlFor=\"penaltyHome\"\n className=\"w-20\"\n >\n <input\n id=\"penaltyHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyHome}\n onChange={(e) => setPenaltyHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"penaltyAway\" className=\"w-20\">\n <input\n id=\"penaltyAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyAway}\n onChange={(e) => setPenaltyAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n )}\n </div>\n </div>\n )}\n </>\n )}\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 {tr('atheletebridge.pages.fixtureDetail.recordResultAction', '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\" />{' '}\n {tr(\n 'atheletebridge.pages.fixtureDetail.viewStandings',\n `View ${f.competition} standings`\n )}\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';\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 { useScheduledTimeGate } from '../../hooks';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\nimport {\n FIXTURE_CRICKET_SCORECARD_LIVE,\n FIXTURE_KNOCKOUT_SCORES_LIVE,\n} from '../../utils/fixtureCapabilities';\nimport {\n buildCricketResultInput,\n buildRecordResultInput,\n parseScoreInput,\n} from '../../utils/recordResult';\nimport { useAbTranslate } from '../../i18n';\n\nexport function FixtureDetailPage() {\n const tr = useAbTranslate();\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 // Knockout outcome (BOFF-7640) — only offered when the fixture is level at\n // full time; a tie still level after extra time must record the shootout.\n const [extraTimeHome, setExtraTimeHome] = useState('');\n const [extraTimeAway, setExtraTimeAway] = useState('');\n const [penaltyHome, setPenaltyHome] = useState('');\n const [penaltyAway, setPenaltyAway] = useState('');\n // Cricket scorecard form (BOFF-7644) — replaces the generic score inputs\n // when the fixture's home team plays cricket.\n const [cricketForm, setCricketForm] = useState({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME' as 'HOME' | 'AWAY',\n decidedBy: 'RUNS' as 'RUNS' | 'WICKETS',\n margin: '',\n superOver: false,\n dls: false,\n });\n const [error, setError] = useState<string | null>(null);\n // Time gate for the completion action; re-renders when kickoff arrives.\n const fixtureStarted = useScheduledTimeGate(fixture.data?.scheduledAt);\n\n const setCricket = <K extends keyof typeof cricketForm>(\n key: K,\n value: (typeof cricketForm)[K]\n ): void => setCricketForm((prev) => ({ ...prev, [key]: value }));\n\n const resetCricketForm = (): void =>\n setCricketForm({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME',\n decidedBy: 'RUNS',\n margin: '',\n superOver: false,\n dls: false,\n });\n\n if (fixture.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <LoadingState\n label={tr('atheletebridge.pages.fixtureDetail.loading', 'Loading fixture…')}\n />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <ErrorState\n title={tr('atheletebridge.pages.fixtureDetail.notFoundTitle', 'Fixture not found')}\n message={tr(\n 'atheletebridge.pages.fixtureDetail.notFoundMessage',\n 'This fixture does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? tr('atheletebridge.pages.fixtureDetail.home', 'Home');\n const awayName =\n f.awayTeam?.name ??\n (f.awayTeamId ? tr('atheletebridge.pages.fixtureDetail.away', 'Away') : 'TBD');\n // Result entry is the completion transition, gated on kickoff here and\n // enforced server-side too. Both branches share isResultEligible so they\n // cannot drift apart.\n const isResultEligible =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n const canRecordResult = isResultEligible && fixtureStarted;\n const awaitingKickoff = isResultEligible && !fixtureStarted;\n\n // The cricket scorecard and knockout inputs post fields the deployed backend\n // may not accept yet; see utils/fixtureCapabilities.\n const isCricket = FIXTURE_CRICKET_SCORECARD_LIVE && f.homeTeam?.sport === 'CRICKET';\n\n // Knockout-outcome inputs (BOFF-7640) — offered once the full-time score is\n // entered and level. The shootout appears while the tie is still level\n // (straight from full time, or after a level extra time).\n const parsedHomeScore = parseScoreInput(homeScore);\n const parsedAwayScore = parseScoreInput(awayScore);\n const fullTimeScoresEntered = parsedHomeScore !== null && parsedAwayScore !== null;\n const levelAtFullTime =\n FIXTURE_KNOCKOUT_SCORES_LIVE && fullTimeScoresEntered && parsedHomeScore === parsedAwayScore;\n const extraTimeEntered = extraTimeHome !== '' || extraTimeAway !== '';\n const extraTimeParsedHome = parseScoreInput(extraTimeHome);\n const extraTimeParsedAway = parseScoreInput(extraTimeAway);\n const extraTimeComplete = extraTimeParsedHome !== null && extraTimeParsedAway !== null;\n const stillLevelAfterExtraTime = extraTimeComplete && extraTimeParsedHome === extraTimeParsedAway;\n const showPenaltyShootout = levelAtFullTime && (!extraTimeEntered || stillLevelAfterExtraTime);\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n\n if (isCricket) {\n const draft = buildCricketResultInput(\n { ...cricketForm },\n {\n required: tr(\n 'atheletebridge.pages.fixtureDetail.cricketRequired',\n 'All innings scores, overs and the margin are required.'\n ),\n negative: tr(\n 'atheletebridge.pages.fixtureDetail.cricketNegative',\n 'Scores and overs cannot be negative.'\n ),\n wicketsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketsRange',\n 'Wickets must be between 0 and 10.'\n ),\n ballsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketBallsRange',\n 'Balls must be between 0 and 5.'\n ),\n marginMinimum: tr(\n 'atheletebridge.pages.fixtureDetail.cricketMarginMinimum',\n 'The margin must be at least 1.'\n ),\n wicketMarginMax: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketMarginMax',\n 'A win by wickets cannot exceed a margin of 10.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n resetCricketForm();\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\n }\n return;\n }\n\n // Non-cricket fixtures: a single pair of scores, optionally followed by a\n // knockout outcome. All the rules live in `buildRecordResultInput` so the\n // page only renders copy and submits the payload it hands back.\n const draft = buildRecordResultInput(\n { homeScore, awayScore, extraTimeHome, extraTimeAway, penaltyHome, penaltyAway },\n {\n scoresRequired: tr(\n 'atheletebridge.pages.fixtureDetail.scoresRequired',\n 'Both scores are required and must be numbers.'\n ),\n scoresNegative: tr(\n 'atheletebridge.pages.fixtureDetail.scoresNegative',\n 'Scores cannot be negative.'\n ),\n extraTimePair: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimePair',\n 'Enter both extra-time scores.'\n ),\n extraTimeNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNumeric',\n 'Extra-time scores must be numbers.'\n ),\n extraTimeNegative: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNegative',\n 'Extra-time scores cannot be negative.'\n ),\n penaltyPair: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyPair',\n 'Enter both penalty scores.'\n ),\n penaltyNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNumeric',\n 'Penalty scores must be numbers.'\n ),\n penaltyNegative: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNegative',\n 'Penalty scores cannot be negative.'\n ),\n extraTimeDecided: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeDecided',\n 'Extra time decided the tie — leave the penalty shootout empty.'\n ),\n extraTimeLevel: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeLevel',\n 'Still level after extra time — record the penalty shootout.'\n ),\n penaltyLevel: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyLevel',\n 'Penalty scores must have a winner.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n setHomeScore('');\n setAwayScore('');\n setExtraTimeHome('');\n setExtraTimeAway('');\n setPenaltyHome('');\n setPenaltyAway('');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\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\" /> {tr('atheletebridge.pages.fixtureDetail.back', '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\">\n {tr('atheletebridge.pages.fixtureDetail.notes', 'Notes')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {awaitingKickoff && (\n <EmphasisPanel className=\"mb-8\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultUnavailable',\n 'A result can be recorded once this fixture has started — after its scheduled date and time.'\n )}\n </p>\n </EmphasisPanel>\n )}\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\" />{' '}\n {tr('atheletebridge.pages.fixtureDetail.recordResult', '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 {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultDescription',\n 'Recording a final score marks this fixture Completed and recomputes the competition standings for both teams.'\n )}\n </p>\n <form onSubmit={handleRecord}>\n {isCricket ? (\n <div className=\"flex flex-col items-center gap-5\">\n <p className=\"text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.cricketScorecardDescription',\n 'Record each innings score and how the match was decided — a cricket result is a margin, not a score comparison.'\n )}\n </p>\n\n {(\n [\n { side: 'home' as const, name: homeName, sport: f.homeTeam?.sport },\n { side: 'away' as const, name: awayName, sport: f.awayTeam?.sport },\n ] as const\n ).map(({ side, name, sport }) => (\n <div key={side} className=\"flex flex-wrap items-end justify-center gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={name} sport={sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {name}\n </span>\n </div>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n htmlFor={`cricket-${side}-runs`}\n className=\"w-20\"\n >\n <input\n id={`cricket-${side}-runs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Runs`]}\n onChange={(e) => setCricket(`${side}Runs`, e.target.value)}\n placeholder=\"185\"\n />\n </FormField>\n <span className=\"pb-3 text-lg font-bold text-text-muted\">/</span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n htmlFor={`cricket-${side}-wickets`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-wickets`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Wickets`]}\n onChange={(e) => setCricket(`${side}Wickets`, e.target.value)}\n placeholder=\"6\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.overs', 'Overs')}\n htmlFor={`cricket-${side}-overs`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-overs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Overs`]}\n onChange={(e) => setCricket(`${side}Overs`, e.target.value)}\n placeholder=\"20\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.balls', 'Balls')}\n htmlFor={`cricket-${side}-balls`}\n className=\"w-14\"\n >\n <input\n id={`cricket-${side}-balls`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Balls`]}\n onChange={(e) => setCricket(`${side}Balls`, e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n ))}\n\n <div className=\"flex flex-wrap items-end justify-center gap-4\">\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.winner', 'Winner')}\n htmlFor=\"cricket-winner\"\n >\n <select\n id=\"cricket-winner\"\n className={fieldInputClass}\n value={cricketForm.winner}\n onChange={(e) => setCricket('winner', e.target.value as 'HOME' | 'AWAY')}\n >\n <option value=\"HOME\">{homeName}</option>\n <option value=\"AWAY\">{awayName}</option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.decidedBy', 'Decided by')}\n htmlFor=\"cricket-decided-by\"\n >\n <select\n id=\"cricket-decided-by\"\n className={fieldInputClass}\n value={cricketForm.decidedBy}\n onChange={(e) =>\n setCricket('decidedBy', e.target.value as 'RUNS' | 'WICKETS')\n }\n >\n <option value=\"RUNS\">\n {tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n </option>\n <option value=\"WICKETS\">\n {tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n </option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.margin', 'Margin')}\n htmlFor=\"cricket-margin\"\n className=\"w-20\"\n >\n <input\n id=\"cricket-margin\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm.margin}\n onChange={(e) => setCricket('margin', e.target.value)}\n placeholder=\"13\"\n />\n </FormField>\n </div>\n\n <div className=\"flex items-center gap-6\">\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.superOver}\n onChange={(e) => setCricket('superOver', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.superOver', 'Super Over')}\n </label>\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.dls}\n onChange={(e) => setCricket('dls', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.dls', 'DLS')}\n </label>\n </div>\n </div>\n ) : (\n <>\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\n label={tr('atheletebridge.pages.fixtureDetail.home', 'Home')}\n htmlFor=\"homeScore\"\n className=\"w-24\"\n >\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\n label={tr('atheletebridge.pages.fixtureDetail.away', 'Away')}\n htmlFor=\"awayScore\"\n className=\"w-24\"\n >\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 {levelAtFullTime && (\n <div className=\"mt-6 border-t border-border-seam pt-5\">\n <p className=\"mb-4 text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.levelAtFullTime',\n 'Level at full time — record how the tie was decided (optional).'\n )}\n </p>\n <div className=\"flex flex-col items-center gap-4\">\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.extraTime', 'Extra time')}\n htmlFor=\"extraTimeHome\"\n className=\"w-20\"\n >\n <input\n id=\"extraTimeHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeHome}\n onChange={(e) => setExtraTimeHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"extraTimeAway\" className=\"w-20\">\n <input\n id=\"extraTimeAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeAway}\n onChange={(e) => setExtraTimeAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n\n {showPenaltyShootout && (\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr(\n 'atheletebridge.pages.fixtureDetail.penaltyShootout',\n 'Penalties'\n )}\n htmlFor=\"penaltyHome\"\n className=\"w-20\"\n >\n <input\n id=\"penaltyHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyHome}\n onChange={(e) => setPenaltyHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"penaltyAway\" className=\"w-20\">\n <input\n id=\"penaltyAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyAway}\n onChange={(e) => setPenaltyAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n )}\n </div>\n </div>\n )}\n </>\n )}\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 {tr('atheletebridge.pages.fixtureDetail.recordResultAction', '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\" />{' '}\n {tr(\n 'atheletebridge.pages.fixtureDetail.viewStandings',\n `View ${f.competition} standings`\n )}\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * My registrations (families)
3
+ * @module atheletebridge/pages/registrations
4
+ *
5
+ * Every registration the signed-in user submitted through a club's share
6
+ * link, with its review and payment status (BOFF-4135). Unpaid registrations
7
+ * the club has not reviewed yet can be withdrawn here.
8
+ */
9
+ export declare function MyRegistrationsPage(): import("react").JSX.Element;
10
+ //# sourceMappingURL=MyRegistrationsPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MyRegistrationsPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/registrations/MyRegistrationsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,wBAAgB,mBAAmB,gCAwFlC"}
@@ -0,0 +1,89 @@
1
+ import { PageLayout as e } from "../../components/PageLayout.js";
2
+ import { StatusBadge as t } from "../../components/StatusBadge.js";
3
+ import { formatDate as n } from "../../utils/formatters.js";
4
+ import { EmptyState as r } from "../../components/EmptyState.js";
5
+ import { ErrorState as i, LoadingState as a } from "../../components/LoadingState.js";
6
+ import { CursorPager as o } from "../../components/CursorPager.js";
7
+ import { useMyRegistrations as s, useWithdrawMyRegistration as c } from "../../hooks/useRegistrationApi.js";
8
+ import { formatMoney as l } from "../../utils/money.js";
9
+ import { useState as u } from "react";
10
+ import { ClipboardList as d, Undo2 as f } from "lucide-react";
11
+ import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
12
+ //#region src/atheletebridge/pages/registrations/MyRegistrationsPage.tsx
13
+ var g = 20;
14
+ function _() {
15
+ let [_, v] = u(0), y = s({
16
+ limit: g,
17
+ offset: _ * g
18
+ }), b = c(), [x, S] = u(null), C = y.data?.items ?? [], w = async (e) => {
19
+ if (window.confirm("Withdraw this registration?")) {
20
+ S(null);
21
+ try {
22
+ await b.mutateAsync(e);
23
+ } catch (e) {
24
+ S(e instanceof Error ? e.message : "Could not withdraw the registration.");
25
+ }
26
+ }
27
+ };
28
+ return /* @__PURE__ */ h(e, {
29
+ title: "My registrations",
30
+ description: "Registrations you have submitted to clubs and academies, and what is still due.",
31
+ children: [x && /* @__PURE__ */ m("p", {
32
+ role: "alert",
33
+ className: "mb-4 text-sm text-status-error-text",
34
+ children: x
35
+ }), y.isLoading ? /* @__PURE__ */ m(a, { label: "Loading your registrations…" }) : y.isError ? /* @__PURE__ */ m(i, { onRetry: () => void y.refetch() }) : C.length === 0 ? /* @__PURE__ */ m(r, {
36
+ illustration: "empty-data",
37
+ icon: /* @__PURE__ */ m(d, { className: "h-10 w-10" }),
38
+ title: "No registrations yet",
39
+ description: "When you register through a club's link, it shows up here."
40
+ }) : /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m("ul", {
41
+ className: "divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface",
42
+ "data-testid": "my-registrations",
43
+ children: C.map((e) => /* @__PURE__ */ h("li", {
44
+ className: "flex flex-wrap items-center justify-between gap-3 px-4 py-3",
45
+ children: [/* @__PURE__ */ h("span", {
46
+ className: "min-w-0",
47
+ children: [/* @__PURE__ */ m("span", {
48
+ className: "block font-medium text-text-primary",
49
+ children: e.participantName
50
+ }), /* @__PURE__ */ h("span", {
51
+ className: "block text-xs text-text-secondary",
52
+ children: [
53
+ e.formTitle,
54
+ " · ",
55
+ n(e.submittedAt)
56
+ ]
57
+ })]
58
+ }), /* @__PURE__ */ h("span", {
59
+ className: "flex flex-wrap items-center gap-2",
60
+ children: [
61
+ /* @__PURE__ */ m(t, { status: e.status }),
62
+ /* @__PURE__ */ m(t, { status: e.paymentStatus }),
63
+ e.amountDue > 0 && /* @__PURE__ */ h("span", {
64
+ className: "text-sm tabular-nums text-text-secondary",
65
+ children: [l(e.amountOutstanding, e.currency), " due"]
66
+ }),
67
+ e.status === "SUBMITTED" && e.amountPaid === 0 && /* @__PURE__ */ h("button", {
68
+ type: "button",
69
+ disabled: b.isPending,
70
+ onClick: () => void w(e.id),
71
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50",
72
+ children: [/* @__PURE__ */ m(f, { className: "h-4 w-4" }), " Withdraw"]
73
+ })
74
+ ]
75
+ })]
76
+ }, e.id))
77
+ }), /* @__PURE__ */ m(o, {
78
+ className: "mt-6",
79
+ page: _,
80
+ hasMore: y.data?.pageInfo.hasMore ?? !1,
81
+ onPrev: () => v((e) => Math.max(0, e - 1)),
82
+ onNext: () => v((e) => e + 1)
83
+ })] })]
84
+ });
85
+ }
86
+ //#endregion
87
+ export { _ as MyRegistrationsPage };
88
+
89
+ //# sourceMappingURL=MyRegistrationsPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MyRegistrationsPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/registrations/MyRegistrationsPage.tsx"],"sourcesContent":["/**\n * My registrations (families)\n * @module atheletebridge/pages/registrations\n *\n * Every registration the signed-in user submitted through a club's share\n * link, with its review and payment status (BOFF-4135). Unpaid registrations\n * the club has not reviewed yet can be withdrawn here.\n */\n\nimport { useState } from 'react';\nimport { ClipboardList, Undo2 } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { CursorPager } from '../../components/CursorPager';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { useMyRegistrations, useWithdrawMyRegistration } from '../../hooks/useRegistrationApi';\nimport { formatDate } from '../../utils/formatters';\nimport { formatMoney } from '../../utils/money';\n\nconst PAGE_SIZE = 20;\n\nexport function MyRegistrationsPage() {\n const [page, setPage] = useState(0);\n const mine = useMyRegistrations({ limit: PAGE_SIZE, offset: page * PAGE_SIZE });\n const withdraw = useWithdrawMyRegistration();\n const [error, setError] = useState<string | null>(null);\n const items = mine.data?.items ?? [];\n\n const onWithdraw = async (id: string) => {\n if (!window.confirm('Withdraw this registration?')) return;\n setError(null);\n try {\n await withdraw.mutateAsync(id);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not withdraw the registration.');\n }\n };\n\n return (\n <PageLayout\n title=\"My registrations\"\n description=\"Registrations you have submitted to clubs and academies, and what is still due.\"\n >\n {error && (\n <p role=\"alert\" className=\"mb-4 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n {mine.isLoading ? (\n <LoadingState label=\"Loading your registrations…\" />\n ) : mine.isError ? (\n <ErrorState onRetry={() => void mine.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title=\"No registrations yet\"\n description=\"When you register through a club's link, it shows up here.\"\n />\n ) : (\n <>\n <ul\n className=\"divide-y divide-border-seam rounded-lg border border-border-seam bg-bg-surface\"\n data-testid=\"my-registrations\"\n >\n {items.map((row) => (\n <li\n key={row.id}\n className=\"flex flex-wrap items-center justify-between gap-3 px-4 py-3\"\n >\n <span className=\"min-w-0\">\n <span className=\"block font-medium text-text-primary\">{row.participantName}</span>\n <span className=\"block text-xs text-text-secondary\">\n {row.formTitle} · {formatDate(row.submittedAt)}\n </span>\n </span>\n <span className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={row.status} />\n <StatusBadge status={row.paymentStatus} />\n {row.amountDue > 0 && (\n <span className=\"text-sm tabular-nums text-text-secondary\">\n {formatMoney(row.amountOutstanding, row.currency)} due\n </span>\n )}\n {row.status === 'SUBMITTED' && row.amountPaid === 0 && (\n <button\n type=\"button\"\n disabled={withdraw.isPending}\n onClick={() => void onWithdraw(row.id)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Undo2 className=\"h-4 w-4\" /> Withdraw\n </button>\n )}\n </span>\n </li>\n ))}\n </ul>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={mine.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,IAAM,IAAY;AAElB,SAAgB,IAAsB;CACpC,IAAM,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,IAAO,EAAmB;EAAE,OAAO;EAAW,QAAQ,IAAO;CAAU,CAAC,GACxE,IAAW,EAA0B,GACrC,CAAC,GAAO,KAAY,EAAwB,IAAI,GAChD,IAAQ,EAAK,MAAM,SAAS,CAAC,GAE7B,IAAa,OAAO,MAAe;EAClC,WAAO,QAAQ,6BAA6B,GACjD;KAAS,IAAI;GACb,IAAI;IACF,MAAM,EAAS,YAAY,CAAE;GAC/B,SAAS,GAAK;IACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,sCAAsC;GACtF;EALa;CAMf;CAEA,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAFd,CAIG,KACC,kBAAC,KAAD;GAAG,MAAK;GAAQ,WAAU;aACvB;EACA,CAAA,GAEJ,EAAK,YACJ,kBAAC,GAAD,EAAc,OAAM,8BAA+B,CAAA,IACjD,EAAK,UACP,kBAAC,GAAD,EAAY,eAAe,KAAK,EAAK,QAAQ,EAAI,CAAA,IAC/C,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAe,WAAU,YAAa,CAAA;GAC5C,OAAM;GACN,aAAY;EACb,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,MAAD;GACE,WAAU;GACV,eAAY;aAEX,EAAM,KAAK,MACV,kBAAC,MAAD;IAEE,WAAU;cAFZ,CAIE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAuC,EAAI;KAAsB,CAAA,GACjF,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAI;OAAU;OAAI,EAAW,EAAI,WAAW;MACzC;OACF;QACN,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,GAAD,EAAa,QAAQ,EAAI,OAAS,CAAA;MAClC,kBAAC,GAAD,EAAa,QAAQ,EAAI,cAAgB,CAAA;MACxC,EAAI,YAAY,KACf,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,EAAY,EAAI,mBAAmB,EAAI,QAAQ,GAAE,MAC9C;;MAEP,EAAI,WAAW,eAAe,EAAI,eAAe,KAChD,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,EAAS;OACnB,eAAe,KAAK,EAAW,EAAI,EAAE;OACrC,WAAU;iBAJZ,CAME,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA,GAAC,WACvB;;KAEN;MACJ;MA5BG,EAAI,EA4BP,CACL;EACC,CAAA,GACJ,kBAAC,GAAD;GACE,WAAU;GACJ;GACN,SAAS,EAAK,MAAM,SAAS,WAAW;GACxC,cAAc,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;GAC/C,cAAc,GAAS,MAAM,IAAI,CAAC;EACnC,CAAA,CACD,EAAA,CAAA,CAEM;;AAEhB"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Record a registration (club staff)
3
+ * @module atheletebridge/pages/registrations
4
+ *
5
+ * Key in a paper, phone or in-person registration against a form (BOFF-4135).
6
+ * Staff entries are accepted for draft and closed forms too (late paper
7
+ * forms, pre-season sign-ups) — only archived forms are off limits.
8
+ */
9
+ export declare function RecordRegistrationPage(): import("react").JSX.Element;
10
+ //# sourceMappingURL=RecordRegistrationPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordRegistrationPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/registrations/RecordRegistrationPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH,wBAAgB,sBAAsB,gCAmGrC"}
@@ -0,0 +1,94 @@
1
+ import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
2
+ import { PageLayout as t } from "../../components/PageLayout.js";
3
+ import { ErrorState as n, LoadingState as r } from "../../components/LoadingState.js";
4
+ import { useRecordRegistration as i, useRegistrationForm as a } from "../../hooks/useRegistrationApi.js";
5
+ import { EMPTY_REGISTRATION_ENTRY as o, cleanAnswers as s, missingRequiredAnswer as c } from "../../components/registration/entryRules.js";
6
+ import { RegistrationEntryFields as l } from "../../components/registration/RegistrationEntryFields.js";
7
+ import { useState as u } from "react";
8
+ import { ArrowLeft as d, Loader2 as f, Save as p } from "lucide-react";
9
+ import { useParams as m } from "react-router";
10
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
11
+ //#region src/atheletebridge/pages/registrations/RecordRegistrationPage.tsx
12
+ function _() {
13
+ let { id: _ } = m(), { navigateTo: v } = e(), y = a(_), b = i(), [x, S] = u(o), [C, w] = u(null);
14
+ if (y.isLoading) return /* @__PURE__ */ h(t, {
15
+ title: "Record registration",
16
+ children: /* @__PURE__ */ h(r, { label: "Loading registration form…" })
17
+ });
18
+ if (y.isError || !y.data || !_) return /* @__PURE__ */ h(t, {
19
+ title: "Record registration",
20
+ children: /* @__PURE__ */ h(n, {
21
+ title: "Registration form not found",
22
+ onRetry: () => v("/registrations")
23
+ })
24
+ });
25
+ let T = y.data;
26
+ return /* @__PURE__ */ h(t, {
27
+ title: "Record registration",
28
+ description: `${T.title} — enter a registration received on paper, by phone or in person.`,
29
+ actions: /* @__PURE__ */ g("button", {
30
+ type: "button",
31
+ onClick: () => v(`/registrations/${_}`),
32
+ 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",
33
+ children: [/* @__PURE__ */ h(d, { className: "h-4 w-4" }), " Cancel"]
34
+ }),
35
+ children: /* @__PURE__ */ g("form", {
36
+ onSubmit: async (e) => {
37
+ e.preventDefault(), w(null);
38
+ let t = c(T.fields, x);
39
+ if (t) {
40
+ w(`Please answer "${t}".`);
41
+ return;
42
+ }
43
+ if (T.waiverText && !x.waiverAccepted) {
44
+ w("Confirm the registrant accepted the waiver.");
45
+ return;
46
+ }
47
+ try {
48
+ v(`/registrations/${_}/submissions/${(await b.mutateAsync({
49
+ formId: _,
50
+ participantName: x.participantName.trim(),
51
+ contactName: x.contactName.trim() || void 0,
52
+ contactEmail: x.contactEmail.trim() || void 0,
53
+ contactPhone: x.contactPhone.trim() || void 0,
54
+ answers: s(x.answers),
55
+ selectedFeeIds: x.optionalFeeIds,
56
+ waiverAccepted: x.waiverAccepted
57
+ })).id}`);
58
+ } catch (e) {
59
+ w(e instanceof Error ? e.message : "Could not record the registration.");
60
+ }
61
+ },
62
+ className: "max-w-2xl space-y-6",
63
+ children: [
64
+ /* @__PURE__ */ h(l, {
65
+ fields: T.fields,
66
+ fees: T.fees,
67
+ currency: T.currency,
68
+ waiverText: T.waiverText,
69
+ state: x,
70
+ onChange: S
71
+ }),
72
+ C && /* @__PURE__ */ h("p", {
73
+ role: "alert",
74
+ className: "text-sm text-status-error-text",
75
+ children: C
76
+ }),
77
+ /* @__PURE__ */ g("button", {
78
+ type: "submit",
79
+ disabled: b.isPending,
80
+ 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 disabled:opacity-60",
81
+ children: [
82
+ b.isPending ? /* @__PURE__ */ h(f, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ h(p, { className: "h-4 w-4" }),
83
+ " ",
84
+ "Save registration"
85
+ ]
86
+ })
87
+ ]
88
+ })
89
+ });
90
+ }
91
+ //#endregion
92
+ export { _ as RecordRegistrationPage };
93
+
94
+ //# sourceMappingURL=RecordRegistrationPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordRegistrationPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/registrations/RecordRegistrationPage.tsx"],"sourcesContent":["/**\n * Record a registration (club staff)\n * @module atheletebridge/pages/registrations\n *\n * Key in a paper, phone or in-person registration against a form (BOFF-4135).\n * Staff entries are accepted for draft and closed forms too (late paper\n * forms, pre-season sign-ups) — only archived forms are off limits.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { RegistrationEntryFields } from '../../components/registration/RegistrationEntryFields';\nimport {\n EMPTY_REGISTRATION_ENTRY,\n type RegistrationEntryState,\n cleanAnswers,\n missingRequiredAnswer,\n} from '../../components/registration/entryRules';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useRecordRegistration, useRegistrationForm } from '../../hooks/useRegistrationApi';\n\nexport function RecordRegistrationPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n const form = useRegistrationForm(id);\n const record = useRecordRegistration();\n const [state, setState] = useState<RegistrationEntryState>(EMPTY_REGISTRATION_ENTRY);\n const [error, setError] = useState<string | null>(null);\n\n if (form.isLoading) {\n return (\n <PageLayout title=\"Record registration\">\n <LoadingState label=\"Loading registration form…\" />\n </PageLayout>\n );\n }\n if (form.isError || !form.data || !id) {\n return (\n <PageLayout title=\"Record registration\">\n <ErrorState\n title=\"Registration form not found\"\n onRetry={() => navigateTo('/registrations')}\n />\n </PageLayout>\n );\n }\n const data = form.data;\n\n const submit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n const missing = missingRequiredAnswer(data.fields, state);\n if (missing) {\n setError(`Please answer \"${missing}\".`);\n return;\n }\n if (data.waiverText && !state.waiverAccepted) {\n setError('Confirm the registrant accepted the waiver.');\n return;\n }\n try {\n const created = await record.mutateAsync({\n formId: id,\n participantName: state.participantName.trim(),\n contactName: state.contactName.trim() || undefined,\n contactEmail: state.contactEmail.trim() || undefined,\n contactPhone: state.contactPhone.trim() || undefined,\n answers: cleanAnswers(state.answers),\n selectedFeeIds: state.optionalFeeIds,\n waiverAccepted: state.waiverAccepted,\n });\n navigateTo(`/registrations/${id}/submissions/${created.id}`);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not record the registration.');\n }\n };\n\n return (\n <PageLayout\n title=\"Record registration\"\n description={`${data.title} — enter a registration received on paper, by phone or in person.`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(`/registrations/${id}`)}\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={submit} className=\"max-w-2xl space-y-6\">\n <RegistrationEntryFields\n fields={data.fields}\n fees={data.fees}\n currency={data.currency}\n waiverText={data.waiverText}\n state={state}\n onChange={setState}\n />\n {error && (\n <p role=\"alert\" className=\"text-sm text-status-error-text\">\n {error}\n </p>\n )}\n <button\n type=\"submit\"\n disabled={record.isPending}\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 disabled:opacity-60\"\n >\n {record.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}{' '}\n Save registration\n </button>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAwBA,SAAgB,IAAyB;CACvC,IAAM,EAAE,UAAO,EAA0B,GACnC,EAAE,kBAAe,EAAiB,GAClC,IAAO,EAAoB,CAAE,GAC7B,IAAS,EAAsB,GAC/B,CAAC,GAAO,KAAY,EAAiC,CAAwB,GAC7E,CAAC,GAAO,KAAY,EAAwB,IAAI;CAEtD,IAAI,EAAK,WACP,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAc,OAAM,6BAA8B,CAAA;CACxC,CAAA;CAGhB,IAAI,EAAK,WAAW,CAAC,EAAK,QAAQ,CAAC,GACjC,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,eAAe,EAAW,gBAAgB;EAC3C,CAAA;CACS,CAAA;CAGhB,IAAM,IAAO,EAAK;CA+BlB,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,GAAG,EAAK,MAAM;EAC3B,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,kBAAkB,GAAI;GAChD,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,GAAC,SAC3B;;YAGV,kBAAC,QAAD;GAAM,UAAU,OA3CE,MAAuB;IAE3C,AADA,EAAE,eAAe,GACjB,EAAS,IAAI;IACb,IAAM,IAAU,EAAsB,EAAK,QAAQ,CAAK;IACxD,IAAI,GAAS;KACX,EAAS,kBAAkB,EAAQ,GAAG;KACtC;IACF;IACA,IAAI,EAAK,cAAc,CAAC,EAAM,gBAAgB;KAC5C,EAAS,6CAA6C;KACtD;IACF;IACA,IAAI;KAWF,EAAW,kBAAkB,EAAG,gBAAe,MAVzB,EAAO,YAAY;MACvC,QAAQ;MACR,iBAAiB,EAAM,gBAAgB,KAAK;MAC5C,aAAa,EAAM,YAAY,KAAK,KAAK,KAAA;MACzC,cAAc,EAAM,aAAa,KAAK,KAAK,KAAA;MAC3C,cAAc,EAAM,aAAa,KAAK,KAAK,KAAA;MAC3C,SAAS,EAAa,EAAM,OAAO;MACnC,gBAAgB,EAAM;MACtB,gBAAgB,EAAM;KACxB,CAAC,GACsD,IAAI;IAC7D,SAAS,GAAK;KACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,oCAAoC;IACpF;GACF;GAgB4B,WAAU;aAAlC;IACE,kBAAC,GAAD;KACE,QAAQ,EAAK;KACb,MAAM,EAAK;KACX,UAAU,EAAK;KACf,YAAY,EAAK;KACV;KACP,UAAU;IACX,CAAA;IACA,KACC,kBAAC,KAAD;KAAG,MAAK;KAAQ,WAAU;eACvB;IACA,CAAA;IAEL,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAO;KACjB,WAAU;eAHZ;MAKG,EAAO,YACN,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,IAE3C,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;MAC1B;MAAI;KAED;;GACJ;;CACI,CAAA;AAEhB"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Family registration (share link)
3
+ * @module atheletebridge/pages/registrations
4
+ *
5
+ * Opened from a club's share link (`/register/:slug`). Any signed-in user —
6
+ * typically a parent who is not a member of the club's workspace — can fill
7
+ * in a published form; the registration is filed with the club and tracked
8
+ * under "My registrations" (BOFF-4135).
9
+ */
10
+ export declare function RegisterPage(): import("react").JSX.Element;
11
+ //# sourceMappingURL=RegisterPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegisterPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/registrations/RegisterPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsBH,wBAAgB,YAAY,gCAgK3B"}
@@ -0,0 +1,160 @@
1
+ import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
2
+ import { PageLayout as t } from "../../components/PageLayout.js";
3
+ import { StatusBadge as n } from "../../components/StatusBadge.js";
4
+ import { formatDate as r } from "../../utils/formatters.js";
5
+ import { EmptyState as i } from "../../components/EmptyState.js";
6
+ import { ErrorState as a, LoadingState as o } from "../../components/LoadingState.js";
7
+ import { usePublicRegistrationForm as s, useSubmitRegistration as c } from "../../hooks/useRegistrationApi.js";
8
+ import { formatMoney as l } from "../../utils/money.js";
9
+ import { EMPTY_REGISTRATION_ENTRY as u, cleanAnswers as d, missingRequiredAnswer as f } from "../../components/registration/entryRules.js";
10
+ import { RegistrationEntryFields as p } from "../../components/registration/RegistrationEntryFields.js";
11
+ import { useState as m } from "react";
12
+ import { CheckCircle2 as h, ClipboardX as g, Loader2 as _, Send as v } from "lucide-react";
13
+ import { useParams as y } from "react-router";
14
+ import { jsx as b, jsxs as x } from "react/jsx-runtime";
15
+ //#region src/atheletebridge/pages/registrations/RegisterPage.tsx
16
+ function S() {
17
+ let { slug: S } = y(), { navigateTo: C } = e(), w = s(S), T = c(), [E, D] = m(u), [O, k] = m(null), [A, j] = m(null);
18
+ if (w.isLoading) return /* @__PURE__ */ b(t, {
19
+ title: "Registration",
20
+ children: /* @__PURE__ */ b(o, { label: "Loading registration form…" })
21
+ });
22
+ if (w.isError) return /* @__PURE__ */ b(t, {
23
+ title: "Registration",
24
+ children: /* @__PURE__ */ b(a, { onRetry: () => void w.refetch() })
25
+ });
26
+ if (!w.data || !S) return /* @__PURE__ */ b(t, {
27
+ title: "Registration",
28
+ children: /* @__PURE__ */ b(i, {
29
+ illustration: "empty-data",
30
+ icon: /* @__PURE__ */ b(g, { className: "h-10 w-10" }),
31
+ title: "This registration link is not available",
32
+ description: "The club may not have published it yet, or the link is incorrect. Check the link with the club."
33
+ })
34
+ });
35
+ let M = w.data;
36
+ if (A) return /* @__PURE__ */ b(t, {
37
+ title: M.title,
38
+ children: /* @__PURE__ */ x("div", {
39
+ className: "max-w-xl space-y-4 rounded-lg border border-border-seam bg-bg-surface p-6",
40
+ "data-testid": "registration-receipt",
41
+ children: [
42
+ /* @__PURE__ */ x("p", {
43
+ className: "flex items-center gap-2 text-lg font-semibold text-text-primary",
44
+ children: [/* @__PURE__ */ b(h, { className: "h-6 w-6 text-status-success-text" }), " Registration received"]
45
+ }),
46
+ /* @__PURE__ */ x("p", {
47
+ className: "text-sm text-text-secondary",
48
+ children: [
49
+ A.participantName,
50
+ " is registered for ",
51
+ A.formTitle,
52
+ ". The club will review it",
53
+ A.amountDue > 0 ? " and confirm payment details with you" : "",
54
+ "."
55
+ ]
56
+ }),
57
+ /* @__PURE__ */ x("div", {
58
+ className: "flex flex-wrap items-center gap-2 text-sm",
59
+ children: [
60
+ /* @__PURE__ */ b(n, { status: A.status }),
61
+ /* @__PURE__ */ b(n, { status: A.paymentStatus }),
62
+ A.amountDue > 0 && /* @__PURE__ */ x("span", {
63
+ className: "tabular-nums text-text-primary",
64
+ children: [l(A.amountOutstanding, A.currency), " due"]
65
+ })
66
+ ]
67
+ }),
68
+ /* @__PURE__ */ b("button", {
69
+ type: "button",
70
+ onClick: () => C("/my-registrations"),
71
+ 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",
72
+ children: "View my registrations"
73
+ })
74
+ ]
75
+ })
76
+ });
77
+ let N = async (e) => {
78
+ e.preventDefault(), k(null);
79
+ let t = f(M.fields, E);
80
+ if (t) {
81
+ k(`Please answer "${t}".`);
82
+ return;
83
+ }
84
+ if (M.waiverText && !E.waiverAccepted) {
85
+ k("Please accept the waiver to register.");
86
+ return;
87
+ }
88
+ try {
89
+ j(await T.mutateAsync({
90
+ slug: S,
91
+ input: {
92
+ participantName: E.participantName.trim(),
93
+ contactName: E.contactName.trim() || void 0,
94
+ contactEmail: E.contactEmail.trim() || void 0,
95
+ contactPhone: E.contactPhone.trim() || void 0,
96
+ answers: d(E.answers),
97
+ selectedFeeIds: E.optionalFeeIds,
98
+ waiverAccepted: E.waiverAccepted
99
+ }
100
+ }));
101
+ } catch (e) {
102
+ k(e instanceof Error ? e.message : "Could not submit the registration.");
103
+ }
104
+ }, P = [
105
+ M.season,
106
+ M.closesAt ? `Closes ${r(M.closesAt)}` : null,
107
+ M.spotsRemaining == null ? null : `${M.spotsRemaining} places left`
108
+ ].filter(Boolean).join(" · ");
109
+ return /* @__PURE__ */ b(t, {
110
+ title: M.title,
111
+ description: P || void 0,
112
+ children: /* @__PURE__ */ x("div", {
113
+ className: "max-w-2xl space-y-6",
114
+ children: [M.description && /* @__PURE__ */ b("p", {
115
+ className: "whitespace-pre-line text-sm text-text-secondary",
116
+ children: M.description
117
+ }), M.isOpen ? /* @__PURE__ */ x("form", {
118
+ onSubmit: N,
119
+ className: "space-y-6",
120
+ children: [
121
+ /* @__PURE__ */ b(p, {
122
+ fields: M.fields,
123
+ fees: M.fees,
124
+ currency: M.currency,
125
+ waiverText: M.waiverText,
126
+ state: E,
127
+ onChange: D
128
+ }),
129
+ O && /* @__PURE__ */ b("p", {
130
+ role: "alert",
131
+ className: "text-sm text-status-error-text",
132
+ children: O
133
+ }),
134
+ /* @__PURE__ */ x("button", {
135
+ type: "submit",
136
+ disabled: T.isPending,
137
+ 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 disabled:opacity-60",
138
+ children: [
139
+ T.isPending ? /* @__PURE__ */ b(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ b(v, { className: "h-4 w-4" }),
140
+ " ",
141
+ "Submit registration"
142
+ ]
143
+ })
144
+ ]
145
+ }) : /* @__PURE__ */ x("p", {
146
+ role: "status",
147
+ className: "rounded-lg border border-status-warning-border bg-status-warning-bg-subtle p-4 text-sm text-status-warning-text",
148
+ children: [
149
+ "This form is not accepting registrations right now",
150
+ M.opensAt && new Date(M.opensAt) > /* @__PURE__ */ new Date() ? ` — it opens ${r(M.opensAt)}` : "",
151
+ "."
152
+ ]
153
+ })]
154
+ })
155
+ });
156
+ }
157
+ //#endregion
158
+ export { S as RegisterPage };
159
+
160
+ //# sourceMappingURL=RegisterPage.js.map