@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegisterPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/registrations/RegisterPage.tsx"],"sourcesContent":["/**\n * Family registration (share link)\n * @module atheletebridge/pages/registrations\n *\n * Opened from a club's share link (`/register/:slug`). Any signed-in user —\n * typically a parent who is not a member of the club's workspace — can fill\n * in a published form; the registration is filed with the club and tracked\n * under \"My registrations\" (BOFF-4135).\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport { CheckCircle2, ClipboardX, Loader2, Send } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { StatusBadge } from '../../components/StatusBadge';\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 { usePublicRegistrationForm, useSubmitRegistration } from '../../hooks/useRegistrationApi';\nimport { formatDate } from '../../utils/formatters';\nimport { formatMoney } from '../../utils/money';\nimport type { AthMyRegistration } from '../../types/registration';\n\nexport function RegisterPage() {\n const { slug } = useParams<{ slug: string }>();\n const { navigateTo } = useAthleteBridge();\n const form = usePublicRegistrationForm(slug);\n const submit = useSubmitRegistration();\n const [state, setState] = useState<RegistrationEntryState>(EMPTY_REGISTRATION_ENTRY);\n const [error, setError] = useState<string | null>(null);\n const [receipt, setReceipt] = useState<AthMyRegistration | null>(null);\n\n if (form.isLoading) {\n return (\n <PageLayout title=\"Registration\">\n <LoadingState label=\"Loading registration form…\" />\n </PageLayout>\n );\n }\n if (form.isError) {\n return (\n <PageLayout title=\"Registration\">\n <ErrorState onRetry={() => void form.refetch()} />\n </PageLayout>\n );\n }\n if (!form.data || !slug) {\n return (\n <PageLayout title=\"Registration\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardX className=\"h-10 w-10\" />}\n title=\"This registration link is not available\"\n description=\"The club may not have published it yet, or the link is incorrect. Check the link with the club.\"\n />\n </PageLayout>\n );\n }\n const data = form.data;\n\n if (receipt) {\n return (\n <PageLayout title={data.title}>\n <div\n className=\"max-w-xl space-y-4 rounded-lg border border-border-seam bg-bg-surface p-6\"\n data-testid=\"registration-receipt\"\n >\n <p className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <CheckCircle2 className=\"h-6 w-6 text-status-success-text\" /> Registration received\n </p>\n <p className=\"text-sm text-text-secondary\">\n {receipt.participantName} is registered for {receipt.formTitle}. The club will review it\n {receipt.amountDue > 0 ? ' and confirm payment details with you' : ''}.\n </p>\n <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n <StatusBadge status={receipt.status} />\n <StatusBadge status={receipt.paymentStatus} />\n {receipt.amountDue > 0 && (\n <span className=\"tabular-nums text-text-primary\">\n {formatMoney(receipt.amountOutstanding, receipt.currency)} due\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/my-registrations')}\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 View my registrations\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const onSubmit = 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('Please accept the waiver to register.');\n return;\n }\n try {\n const result = await submit.mutateAsync({\n slug,\n input: {\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 });\n setReceipt(result);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not submit the registration.');\n }\n };\n\n const subtitle = [\n data.season,\n data.closesAt ? `Closes ${formatDate(data.closesAt)}` : null,\n data.spotsRemaining != null ? `${data.spotsRemaining} places left` : null,\n ]\n .filter(Boolean)\n .join(' · ');\n\n return (\n <PageLayout title={data.title} description={subtitle || undefined}>\n <div className=\"max-w-2xl space-y-6\">\n {data.description && (\n <p className=\"whitespace-pre-line text-sm text-text-secondary\">{data.description}</p>\n )}\n {!data.isOpen ? (\n <p\n role=\"status\"\n className=\"rounded-lg border border-status-warning-border bg-status-warning-bg-subtle p-4 text-sm text-status-warning-text\"\n >\n This form is not accepting registrations right now\n {data.opensAt && new Date(data.opensAt) > new Date()\n ? ` — it opens ${formatDate(data.opensAt)}`\n : ''}\n .\n </p>\n ) : (\n <form onSubmit={onSubmit} className=\"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={submit.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 {submit.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Send className=\"h-4 w-4\" />\n )}{' '}\n Submit registration\n </button>\n </form>\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Registration form builder (create / edit)
3
+ * @module atheletebridge/pages/registrations
4
+ *
5
+ * Club staff define the form's details, its questions and its fees
6
+ * (BOFF-4135). Questions, fees and currency lock once the first registration
7
+ * arrives — the server enforces the same rule.
8
+ */
9
+ export declare function RegistrationFormBuilderPage(): import("react").JSX.Element;
10
+ //# sourceMappingURL=RegistrationFormBuilderPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RegistrationFormBuilderPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/registrations/RegistrationFormBuilderPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiJH,wBAAgB,2BAA2B,gCA+iB1C"}
@@ -0,0 +1,602 @@
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 { useTeams as i } from "../../hooks/useAthleteBridgeApi.js";
5
+ import { FormField as a, fieldInputClass as o } from "../../components/form.js";
6
+ import { useCreateRegistrationForm as s, useRegistrationForm as c, useUpdateRegistrationForm as l } from "../../hooks/useRegistrationApi.js";
7
+ import { minorToInput as u, parseMoney as d } from "../../utils/money.js";
8
+ import { keyFromLabel as f } from "../../utils/registration.js";
9
+ import { useEffect as p, useRef as m, useState as h } from "react";
10
+ import { ArrowDown as g, ArrowLeft as _, ArrowUp as v, Loader2 as y, Plus as b, Save as x, Trash2 as S } from "lucide-react";
11
+ import { useParams as C } from "react-router";
12
+ import { jsx as w, jsxs as T } from "react/jsx-runtime";
13
+ //#region src/atheletebridge/pages/registrations/RegistrationFormBuilderPage.tsx
14
+ var E = {
15
+ SHORT_TEXT: "Short text",
16
+ LONG_TEXT: "Paragraph",
17
+ EMAIL: "Email",
18
+ PHONE: "Phone",
19
+ NUMBER: "Number",
20
+ DATE: "Date",
21
+ SELECT: "Dropdown",
22
+ CHECKBOX: "Checkbox (yes / consent)"
23
+ }, D = [
24
+ "USD",
25
+ "INR",
26
+ "GBP",
27
+ "EUR",
28
+ "AUD",
29
+ "CAD",
30
+ "SGD",
31
+ "AED"
32
+ ], O = () => `draft-${crypto.randomUUID()}`, k = {
33
+ title: "",
34
+ description: "",
35
+ season: "",
36
+ teamId: "",
37
+ slug: "",
38
+ currency: "USD",
39
+ opensAt: "",
40
+ closesAt: "",
41
+ maxSubmissions: "",
42
+ waiverText: "",
43
+ questions: [],
44
+ fees: []
45
+ };
46
+ function A(e) {
47
+ if (!e) return "";
48
+ let t = new Date(e), n = t.getTimezoneOffset() * 6e4;
49
+ return new Date(t.getTime() - n).toISOString().slice(0, 16);
50
+ }
51
+ function j(e) {
52
+ return e ? new Date(e).toISOString() : void 0;
53
+ }
54
+ function M(e) {
55
+ return {
56
+ title: e.title,
57
+ description: e.description ?? "",
58
+ season: e.season ?? "",
59
+ teamId: e.teamId ?? "",
60
+ slug: e.slug,
61
+ currency: e.currency,
62
+ opensAt: A(e.opensAt),
63
+ closesAt: A(e.closesAt),
64
+ maxSubmissions: e.maxSubmissions == null ? "" : String(e.maxSubmissions),
65
+ waiverText: e.waiverText ?? "",
66
+ questions: e.fields.map((e) => ({
67
+ uid: O(),
68
+ key: e.key,
69
+ label: e.label,
70
+ fieldType: e.fieldType,
71
+ required: e.required,
72
+ optionsText: e.options.join(", "),
73
+ helpText: e.helpText ?? ""
74
+ })),
75
+ fees: e.fees.map((t) => ({
76
+ uid: O(),
77
+ name: t.name,
78
+ amountText: u(t.amount, e.currency),
79
+ feeType: t.feeType,
80
+ description: t.description ?? ""
81
+ }))
82
+ };
83
+ }
84
+ function N(e, t, n) {
85
+ let r = t + n;
86
+ if (r < 0 || r >= e.length) return e;
87
+ let i = [...e], [a] = i.splice(t, 1);
88
+ return a !== void 0 && i.splice(r, 0, a), i;
89
+ }
90
+ var P = "inline-flex h-8 w-8 items-center justify-center rounded-md border border-border-seam text-text-secondary transition-colors hover:bg-bg-sunken disabled:opacity-40";
91
+ function F() {
92
+ let { id: u } = C(), A = !!u, { navigateTo: F } = e(), I = c(u), L = i({ pagination: { limit: 100 } }), R = s(), z = l(), [B, V] = h(k), [H, U] = h(null), W = m(null);
93
+ p(() => {
94
+ A && I.data && W.current !== I.data.id && (W.current = I.data.id, V(M(I.data)));
95
+ }, [A, I.data]);
96
+ let G = A && (I.data?.submissionCount ?? 0) > 0, K = (e, t) => V((n) => ({
97
+ ...n,
98
+ [e]: t
99
+ })), q = (e, t) => K("questions", B.questions.map((n) => n.uid === e ? {
100
+ ...n,
101
+ ...t
102
+ } : n)), J = (e, t) => K("fees", B.fees.map((n) => n.uid === e ? {
103
+ ...n,
104
+ ...t
105
+ } : n)), Y = () => {
106
+ let e = new Set(B.questions.map((e) => e.key).filter(Boolean)), t = [];
107
+ for (let n of B.questions) {
108
+ let r = n.label.trim();
109
+ if (!r) return "Every question needs a label.";
110
+ let i = n.key || f(r, e);
111
+ e.add(i);
112
+ let a = n.optionsText.split(",").map((e) => e.trim()).filter(Boolean);
113
+ if (n.fieldType === "SELECT" && a.length === 0) return `Add at least one option to the dropdown "${r}".`;
114
+ t.push({
115
+ key: i,
116
+ label: r,
117
+ fieldType: n.fieldType,
118
+ required: n.required,
119
+ options: n.fieldType === "SELECT" ? a : void 0,
120
+ helpText: n.helpText.trim() || void 0
121
+ });
122
+ }
123
+ let n = [];
124
+ for (let e of B.fees) {
125
+ let t = e.name.trim();
126
+ if (!t) return "Every fee needs a name.";
127
+ let r = d(e.amountText, B.currency);
128
+ if (r === null) return `Enter a valid amount for "${t}".`;
129
+ n.push({
130
+ name: t,
131
+ amount: r,
132
+ feeType: e.feeType,
133
+ description: e.description.trim() || void 0
134
+ });
135
+ }
136
+ return {
137
+ fields: t,
138
+ fees: n
139
+ };
140
+ }, X = async (e) => {
141
+ if (e.preventDefault(), U(null), !B.title.trim()) {
142
+ U("Give the form a title.");
143
+ return;
144
+ }
145
+ let t = B.maxSubmissions.trim() ? Number.parseInt(B.maxSubmissions, 10) : void 0;
146
+ if (t !== void 0 && (Number.isNaN(t) || t < 1)) {
147
+ U("Capacity must be a positive number.");
148
+ return;
149
+ }
150
+ let n = G ? null : Y();
151
+ if (typeof n == "string") {
152
+ U(n);
153
+ return;
154
+ }
155
+ try {
156
+ A && u ? (await z.mutateAsync({
157
+ id: u,
158
+ input: {
159
+ title: B.title.trim(),
160
+ description: B.description.trim() || null,
161
+ season: B.season.trim() || null,
162
+ teamId: B.teamId || null,
163
+ opensAt: j(B.opensAt) ?? null,
164
+ closesAt: j(B.closesAt) ?? null,
165
+ maxSubmissions: t ?? null,
166
+ waiverText: B.waiverText.trim() || null,
167
+ ...n ? {
168
+ currency: B.currency,
169
+ fields: n.fields,
170
+ fees: n.fees
171
+ } : {}
172
+ }
173
+ }), F(`/registrations/${u}`)) : n && F(`/registrations/${(await R.mutateAsync({
174
+ title: B.title.trim(),
175
+ description: B.description.trim() || void 0,
176
+ season: B.season.trim() || void 0,
177
+ teamId: B.teamId || void 0,
178
+ slug: B.slug.trim() || void 0,
179
+ currency: B.currency,
180
+ opensAt: j(B.opensAt),
181
+ closesAt: j(B.closesAt),
182
+ maxSubmissions: t,
183
+ waiverText: B.waiverText.trim() || void 0,
184
+ fields: n.fields,
185
+ fees: n.fees
186
+ })).id}`);
187
+ } catch (e) {
188
+ U(e instanceof Error ? e.message : "Failed to save the registration form.");
189
+ }
190
+ }, Z = R.isPending || z.isPending, Q = A ? "Edit registration form" : "New registration form";
191
+ return A && I.isLoading ? /* @__PURE__ */ w(t, {
192
+ title: Q,
193
+ children: /* @__PURE__ */ w(r, { label: "Loading registration form…" })
194
+ }) : A && (I.isError || !I.data) ? /* @__PURE__ */ w(t, {
195
+ title: Q,
196
+ children: /* @__PURE__ */ w(n, {
197
+ title: "Registration form not found",
198
+ message: "This form does not exist or you do not have access to it.",
199
+ onRetry: () => F("/registrations")
200
+ })
201
+ }) : /* @__PURE__ */ w(t, {
202
+ title: Q,
203
+ description: "Set the details, the questions families answer and the fees they pay.",
204
+ actions: /* @__PURE__ */ T("button", {
205
+ type: "button",
206
+ onClick: () => F(A && u ? `/registrations/${u}` : "/registrations"),
207
+ 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",
208
+ children: [/* @__PURE__ */ w(_, { className: "h-4 w-4" }), " Cancel"]
209
+ }),
210
+ children: /* @__PURE__ */ T("form", {
211
+ onSubmit: X,
212
+ className: "max-w-3xl space-y-8",
213
+ children: [
214
+ /* @__PURE__ */ T("section", {
215
+ className: "space-y-5",
216
+ children: [
217
+ /* @__PURE__ */ w(a, {
218
+ label: "Form title",
219
+ htmlFor: "title",
220
+ required: !0,
221
+ children: /* @__PURE__ */ w("input", {
222
+ id: "title",
223
+ className: o,
224
+ value: B.title,
225
+ onChange: (e) => K("title", e.target.value),
226
+ placeholder: "e.g. U12 Boys — 2026/27 season registration"
227
+ })
228
+ }),
229
+ /* @__PURE__ */ w(a, {
230
+ label: "Description",
231
+ htmlFor: "description",
232
+ children: /* @__PURE__ */ w("textarea", {
233
+ id: "description",
234
+ rows: 3,
235
+ className: o,
236
+ value: B.description,
237
+ onChange: (e) => K("description", e.target.value),
238
+ placeholder: "Who this is for, training days, what is included."
239
+ })
240
+ }),
241
+ /* @__PURE__ */ T("div", {
242
+ className: "grid grid-cols-1 gap-5 sm:grid-cols-3",
243
+ children: [
244
+ /* @__PURE__ */ w(a, {
245
+ label: "Season",
246
+ htmlFor: "season",
247
+ children: /* @__PURE__ */ w("input", {
248
+ id: "season",
249
+ className: o,
250
+ value: B.season,
251
+ onChange: (e) => K("season", e.target.value),
252
+ placeholder: "2026/27"
253
+ })
254
+ }),
255
+ /* @__PURE__ */ w(a, {
256
+ label: "Team",
257
+ htmlFor: "teamId",
258
+ hint: "Optional squad this registers for.",
259
+ children: /* @__PURE__ */ T("select", {
260
+ id: "teamId",
261
+ className: o,
262
+ value: B.teamId,
263
+ onChange: (e) => K("teamId", e.target.value),
264
+ children: [/* @__PURE__ */ w("option", {
265
+ value: "",
266
+ children: "No specific team"
267
+ }), (L.data?.items ?? []).map((e) => /* @__PURE__ */ w("option", {
268
+ value: e.id,
269
+ children: e.name
270
+ }, e.id))]
271
+ })
272
+ }),
273
+ /* @__PURE__ */ w(a, {
274
+ label: "Currency",
275
+ htmlFor: "currency",
276
+ hint: G ? "Locked after the first registration." : void 0,
277
+ children: /* @__PURE__ */ w("select", {
278
+ id: "currency",
279
+ className: o,
280
+ value: B.currency,
281
+ disabled: G,
282
+ onChange: (e) => K("currency", e.target.value),
283
+ children: [...new Set([B.currency, ...D])].map((e) => /* @__PURE__ */ w("option", {
284
+ value: e,
285
+ children: e
286
+ }, e))
287
+ })
288
+ })
289
+ ]
290
+ }),
291
+ /* @__PURE__ */ T("div", {
292
+ className: "grid grid-cols-1 gap-5 sm:grid-cols-3",
293
+ children: [
294
+ /* @__PURE__ */ w(a, {
295
+ label: "Opens",
296
+ htmlFor: "opensAt",
297
+ hint: "Leave empty to open on publish.",
298
+ children: /* @__PURE__ */ w("input", {
299
+ id: "opensAt",
300
+ type: "datetime-local",
301
+ className: o,
302
+ value: B.opensAt,
303
+ onChange: (e) => K("opensAt", e.target.value)
304
+ })
305
+ }),
306
+ /* @__PURE__ */ w(a, {
307
+ label: "Closes",
308
+ htmlFor: "closesAt",
309
+ hint: "Leave empty to close manually.",
310
+ children: /* @__PURE__ */ w("input", {
311
+ id: "closesAt",
312
+ type: "datetime-local",
313
+ className: o,
314
+ value: B.closesAt,
315
+ onChange: (e) => K("closesAt", e.target.value)
316
+ })
317
+ }),
318
+ /* @__PURE__ */ w(a, {
319
+ label: "Capacity",
320
+ htmlFor: "maxSubmissions",
321
+ hint: "Maximum registrations.",
322
+ children: /* @__PURE__ */ w("input", {
323
+ id: "maxSubmissions",
324
+ inputMode: "numeric",
325
+ className: o,
326
+ value: B.maxSubmissions,
327
+ onChange: (e) => K("maxSubmissions", e.target.value),
328
+ placeholder: "Unlimited"
329
+ })
330
+ })
331
+ ]
332
+ }),
333
+ !A && /* @__PURE__ */ w(a, {
334
+ label: "Share-link name",
335
+ htmlFor: "slug",
336
+ hint: "Optional. Lowercase letters, digits and dashes. Generated from the title when empty.",
337
+ children: /* @__PURE__ */ w("input", {
338
+ id: "slug",
339
+ className: o,
340
+ value: B.slug,
341
+ onChange: (e) => K("slug", e.target.value),
342
+ placeholder: "u12-boys-2026"
343
+ })
344
+ })
345
+ ]
346
+ }),
347
+ /* @__PURE__ */ T("section", {
348
+ className: "space-y-3",
349
+ "aria-labelledby": "builder-questions",
350
+ children: [
351
+ /* @__PURE__ */ T("div", {
352
+ className: "flex items-center justify-between",
353
+ children: [/* @__PURE__ */ w("h2", {
354
+ id: "builder-questions",
355
+ className: "text-base font-semibold text-text-primary",
356
+ children: "Questions"
357
+ }), /* @__PURE__ */ T("button", {
358
+ type: "button",
359
+ disabled: G,
360
+ onClick: () => K("questions", [...B.questions, {
361
+ uid: O(),
362
+ key: "",
363
+ label: "",
364
+ fieldType: "SHORT_TEXT",
365
+ required: !1,
366
+ optionsText: "",
367
+ helpText: ""
368
+ }]),
369
+ 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",
370
+ children: [/* @__PURE__ */ w(b, { className: "h-4 w-4" }), " Add question"]
371
+ })]
372
+ }),
373
+ /* @__PURE__ */ T("p", {
374
+ className: "text-xs text-text-secondary",
375
+ children: ["Participant name and contact details are always collected.", G && " Questions are locked because this form already has registrations."]
376
+ }),
377
+ B.questions.map((e, t) => /* @__PURE__ */ T("div", {
378
+ className: "space-y-3 rounded-lg border border-border-seam bg-bg-surface p-4",
379
+ "data-testid": "builder-question",
380
+ children: [
381
+ /* @__PURE__ */ T("div", {
382
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-[1fr_12rem]",
383
+ children: [/* @__PURE__ */ w(a, {
384
+ label: `Question ${t + 1}`,
385
+ htmlFor: `q-label-${e.uid}`,
386
+ children: /* @__PURE__ */ w("input", {
387
+ id: `q-label-${e.uid}`,
388
+ className: o,
389
+ value: e.label,
390
+ disabled: G,
391
+ onChange: (t) => q(e.uid, { label: t.target.value }),
392
+ placeholder: "e.g. Date of birth"
393
+ })
394
+ }), /* @__PURE__ */ w(a, {
395
+ label: "Answer type",
396
+ htmlFor: `q-type-${e.uid}`,
397
+ children: /* @__PURE__ */ w("select", {
398
+ id: `q-type-${e.uid}`,
399
+ className: o,
400
+ value: e.fieldType,
401
+ disabled: G,
402
+ onChange: (t) => q(e.uid, { fieldType: t.target.value }),
403
+ children: Object.keys(E).map((e) => /* @__PURE__ */ w("option", {
404
+ value: e,
405
+ children: E[e]
406
+ }, e))
407
+ })
408
+ })]
409
+ }),
410
+ e.fieldType === "SELECT" && /* @__PURE__ */ w(a, {
411
+ label: "Options",
412
+ htmlFor: `q-options-${e.uid}`,
413
+ hint: "Comma-separated.",
414
+ children: /* @__PURE__ */ w("input", {
415
+ id: `q-options-${e.uid}`,
416
+ className: o,
417
+ value: e.optionsText,
418
+ disabled: G,
419
+ onChange: (t) => q(e.uid, { optionsText: t.target.value }),
420
+ placeholder: "XS, S, M, L"
421
+ })
422
+ }),
423
+ /* @__PURE__ */ w(a, {
424
+ label: "Help text",
425
+ htmlFor: `q-help-${e.uid}`,
426
+ children: /* @__PURE__ */ w("input", {
427
+ id: `q-help-${e.uid}`,
428
+ className: o,
429
+ value: e.helpText,
430
+ disabled: G,
431
+ onChange: (t) => q(e.uid, { helpText: t.target.value })
432
+ })
433
+ }),
434
+ /* @__PURE__ */ T("div", {
435
+ className: "flex flex-wrap items-center justify-between gap-3",
436
+ children: [/* @__PURE__ */ T("label", {
437
+ className: "flex items-center gap-2 text-sm text-text-primary",
438
+ children: [/* @__PURE__ */ w("input", {
439
+ type: "checkbox",
440
+ checked: e.required,
441
+ disabled: G,
442
+ onChange: (t) => q(e.uid, { required: t.target.checked }),
443
+ className: "h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]"
444
+ }), "Required"]
445
+ }), /* @__PURE__ */ T("span", {
446
+ className: "flex gap-2",
447
+ children: [
448
+ /* @__PURE__ */ w("button", {
449
+ type: "button",
450
+ "aria-label": "Move question up",
451
+ className: P,
452
+ disabled: G || t === 0,
453
+ onClick: () => K("questions", N(B.questions, t, -1)),
454
+ children: /* @__PURE__ */ w(v, { className: "h-4 w-4" })
455
+ }),
456
+ /* @__PURE__ */ w("button", {
457
+ type: "button",
458
+ "aria-label": "Move question down",
459
+ className: P,
460
+ disabled: G || t === B.questions.length - 1,
461
+ onClick: () => K("questions", N(B.questions, t, 1)),
462
+ children: /* @__PURE__ */ w(g, { className: "h-4 w-4" })
463
+ }),
464
+ /* @__PURE__ */ w("button", {
465
+ type: "button",
466
+ "aria-label": "Remove question",
467
+ className: P,
468
+ disabled: G,
469
+ onClick: () => K("questions", B.questions.filter((t) => t.uid !== e.uid)),
470
+ children: /* @__PURE__ */ w(S, { className: "h-4 w-4" })
471
+ })
472
+ ]
473
+ })]
474
+ })
475
+ ]
476
+ }, e.uid))
477
+ ]
478
+ }),
479
+ /* @__PURE__ */ T("section", {
480
+ className: "space-y-3",
481
+ "aria-labelledby": "builder-fees",
482
+ children: [
483
+ /* @__PURE__ */ T("div", {
484
+ className: "flex items-center justify-between",
485
+ children: [/* @__PURE__ */ w("h2", {
486
+ id: "builder-fees",
487
+ className: "text-base font-semibold text-text-primary",
488
+ children: "Fees"
489
+ }), /* @__PURE__ */ T("button", {
490
+ type: "button",
491
+ disabled: G,
492
+ onClick: () => K("fees", [...B.fees, {
493
+ uid: O(),
494
+ name: "",
495
+ amountText: "",
496
+ feeType: "REQUIRED",
497
+ description: ""
498
+ }]),
499
+ 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",
500
+ children: [/* @__PURE__ */ w(b, { className: "h-4 w-4" }), " Add fee"]
501
+ })]
502
+ }),
503
+ /* @__PURE__ */ w("p", {
504
+ className: "text-xs text-text-secondary",
505
+ children: "Required fees are charged on every registration; optional ones (kit, tournament entry) are opt-in. Payments are recorded by the club office as they arrive."
506
+ }),
507
+ B.fees.map((e) => /* @__PURE__ */ T("div", {
508
+ className: "grid grid-cols-1 gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 sm:grid-cols-[1fr_9rem_9rem_auto]",
509
+ "data-testid": "builder-fee",
510
+ children: [
511
+ /* @__PURE__ */ w(a, {
512
+ label: "Fee name",
513
+ htmlFor: `f-name-${e.uid}`,
514
+ children: /* @__PURE__ */ w("input", {
515
+ id: `f-name-${e.uid}`,
516
+ className: o,
517
+ value: e.name,
518
+ disabled: G,
519
+ onChange: (t) => J(e.uid, { name: t.target.value }),
520
+ placeholder: "Membership"
521
+ })
522
+ }),
523
+ /* @__PURE__ */ w(a, {
524
+ label: `Amount (${B.currency})`,
525
+ htmlFor: `f-amount-${e.uid}`,
526
+ children: /* @__PURE__ */ w("input", {
527
+ id: `f-amount-${e.uid}`,
528
+ inputMode: "decimal",
529
+ className: o,
530
+ value: e.amountText,
531
+ disabled: G,
532
+ onChange: (t) => J(e.uid, { amountText: t.target.value }),
533
+ placeholder: "150.00"
534
+ })
535
+ }),
536
+ /* @__PURE__ */ w(a, {
537
+ label: "Type",
538
+ htmlFor: `f-type-${e.uid}`,
539
+ children: /* @__PURE__ */ T("select", {
540
+ id: `f-type-${e.uid}`,
541
+ className: o,
542
+ value: e.feeType,
543
+ disabled: G,
544
+ onChange: (t) => J(e.uid, { feeType: t.target.value }),
545
+ children: [/* @__PURE__ */ w("option", {
546
+ value: "REQUIRED",
547
+ children: "Required"
548
+ }), /* @__PURE__ */ w("option", {
549
+ value: "OPTIONAL",
550
+ children: "Optional"
551
+ })]
552
+ })
553
+ }),
554
+ /* @__PURE__ */ w("div", {
555
+ className: "flex items-end",
556
+ children: /* @__PURE__ */ w("button", {
557
+ type: "button",
558
+ "aria-label": "Remove fee",
559
+ className: P,
560
+ disabled: G,
561
+ onClick: () => K("fees", B.fees.filter((t) => t.uid !== e.uid)),
562
+ children: /* @__PURE__ */ w(S, { className: "h-4 w-4" })
563
+ })
564
+ })
565
+ ]
566
+ }, e.uid))
567
+ ]
568
+ }),
569
+ /* @__PURE__ */ w("section", { children: /* @__PURE__ */ w(a, {
570
+ label: "Waiver / consent",
571
+ htmlFor: "waiverText",
572
+ hint: "Optional. When set, registrants must accept it to submit.",
573
+ children: /* @__PURE__ */ w("textarea", {
574
+ id: "waiverText",
575
+ rows: 4,
576
+ className: o,
577
+ value: B.waiverText,
578
+ onChange: (e) => K("waiverText", e.target.value)
579
+ })
580
+ }) }),
581
+ H && /* @__PURE__ */ w("p", {
582
+ role: "alert",
583
+ className: "text-sm text-status-error-text",
584
+ children: H
585
+ }),
586
+ /* @__PURE__ */ w("div", {
587
+ className: "pt-2",
588
+ children: /* @__PURE__ */ T("button", {
589
+ type: "submit",
590
+ disabled: Z,
591
+ 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",
592
+ children: [Z ? /* @__PURE__ */ w(y, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ w(x, { className: "h-4 w-4" }), A ? "Save changes" : "Create form"]
593
+ })
594
+ })
595
+ ]
596
+ })
597
+ });
598
+ }
599
+ //#endregion
600
+ export { F as RegistrationFormBuilderPage };
601
+
602
+ //# sourceMappingURL=RegistrationFormBuilderPage.js.map