@riverbankcms/sdk 0.72.0 → 0.75.0

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 (146) hide show
  1. package/README.md +1 -1
  2. package/dist/{PreviewEditorSidebar-44T64SAR.mjs → PreviewEditorSidebar-U2IHCBYN.mjs} +3 -3
  3. package/dist/{PreviewEditorUI-4WKHZTXW.mjs → PreviewEditorUI-ZGH2RQNP.mjs} +3 -3
  4. package/dist/SdkPreviewModeRuntime-MTUGBTLQ.mjs +8 -0
  5. package/dist/_dts/ai/src/contracts/proposals.d.ts +10 -10
  6. package/dist/_dts/api/src/cache-tags.d.ts +22 -0
  7. package/dist/_dts/api/src/contentRuntime.d.ts +1 -0
  8. package/dist/_dts/api/src/endpoints.d.ts +24 -24
  9. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +2 -1
  10. package/dist/_dts/block-form/src/blockDraftSaveability.d.ts +4 -1
  11. package/dist/_dts/blocks/src/client/booking-form.d.ts +2 -0
  12. package/dist/_dts/blocks/src/client/carousel.d.ts +1 -0
  13. package/dist/_dts/blocks/src/client/course-registration.d.ts +2 -0
  14. package/dist/_dts/blocks/src/client/events.d.ts +4 -0
  15. package/dist/_dts/blocks/src/client/form.d.ts +2 -0
  16. package/dist/_dts/blocks/src/client/header.d.ts +1 -0
  17. package/dist/_dts/blocks/src/client/location-map.d.ts +1 -0
  18. package/dist/_dts/blocks/src/client/newsletter-form.d.ts +2 -0
  19. package/dist/_dts/blocks/src/client/shop.d.ts +6 -0
  20. package/dist/_dts/blocks/src/lib/video.d.ts +2 -0
  21. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +21 -1
  22. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +6 -6
  23. package/dist/_dts/blocks/src/system/node/fragments/backgroundLayer.d.ts +15 -1
  24. package/dist/_dts/blocks/src/system/node/fragments/styledSection.d.ts +11 -0
  25. package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +1 -0
  26. package/dist/_dts/blocks/src/system/runtime/header/enhanceHeaderSection.d.ts +13 -0
  27. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.headless.state.d.ts +3 -2
  28. package/dist/_dts/blocks/src/system/runtime/nodes/events/index.d.ts +3 -0
  29. package/dist/_dts/blocks/src/system/runtime/nodes/header-section.client.d.ts +1 -0
  30. package/dist/_dts/blocks/src/system/runtime/nodes/header-section.server.d.ts +1 -0
  31. package/dist/_dts/blocks/src/system/runtime/nodes/media.d.ts +6 -0
  32. package/dist/_dts/content-editor/src/hooks/validateBlocksForPublish.d.ts +5 -0
  33. package/dist/_dts/preview-next/src/client/preview/SiteChromeEditableSurface.d.ts +12 -2
  34. package/dist/_dts/sdk/src/public-api/contracts.d.ts +1 -0
  35. package/dist/_dts/sdk/src/rendering/components/Layout.d.ts +2 -2
  36. package/dist/_dts/sdk/src/rendering/islands/deferredVideoEmbeds.d.ts +2 -0
  37. package/dist/_dts/sdk/src/rendering/islands/enhancers/accordion.d.ts +1 -0
  38. package/dist/_dts/sdk/src/rendering/islands/enhancers/bookingForm.d.ts +1 -0
  39. package/dist/_dts/sdk/src/rendering/islands/enhancers/carousel.d.ts +1 -0
  40. package/dist/_dts/sdk/src/rendering/islands/enhancers/cart.d.ts +1 -0
  41. package/dist/_dts/sdk/src/rendering/islands/enhancers/checkout.d.ts +1 -0
  42. package/dist/_dts/sdk/src/rendering/islands/enhancers/courseRegistration.d.ts +1 -0
  43. package/dist/_dts/sdk/src/rendering/islands/enhancers/eventCalendar.d.ts +1 -0
  44. package/dist/_dts/sdk/src/rendering/islands/enhancers/eventCombined.d.ts +1 -0
  45. package/dist/_dts/sdk/src/rendering/islands/enhancers/eventDetails.d.ts +1 -0
  46. package/dist/_dts/sdk/src/rendering/islands/enhancers/eventListing.d.ts +1 -0
  47. package/dist/_dts/sdk/src/rendering/islands/enhancers/eventRegistration.d.ts +1 -0
  48. package/dist/_dts/sdk/src/rendering/islands/enhancers/form.d.ts +1 -0
  49. package/dist/_dts/sdk/src/rendering/islands/enhancers/headerSection.d.ts +1 -0
  50. package/dist/_dts/sdk/src/rendering/islands/enhancers/locationMap.d.ts +1 -0
  51. package/dist/_dts/sdk/src/rendering/islands/enhancers/newsletterForm.d.ts +1 -0
  52. package/dist/_dts/sdk/src/rendering/islands/enhancers/productDetail.d.ts +1 -0
  53. package/dist/_dts/sdk/src/rendering/islands/enhancers/productList.d.ts +1 -0
  54. package/dist/_dts/sdk/src/rendering/islands/enhancers/shop.d.ts +1 -0
  55. package/dist/_dts/sdk/src/rendering/islands/hydrateBlocksIslands.d.ts +2 -2
  56. package/dist/_dts/sdk/src/rendering/islands/propsDecoder.d.ts +7 -0
  57. package/dist/_dts/sdk/src/rendering/islands/types.d.ts +9 -0
  58. package/dist/_dts/sdk/src/rendering/server.d.ts +3 -0
  59. package/dist/_dts/sdk/src/version.d.ts +1 -1
  60. package/dist/_dts/theme-core/src/accordions/types.d.ts +3 -3
  61. package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +7 -7
  62. package/dist/_dts/theme-core/src/buttons/types.d.ts +20 -20
  63. package/dist/_dts/theme-core/src/cards/types.d.ts +2 -2
  64. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  65. package/dist/_dts/theme-core/src/header/navDropdownSizing.d.ts +7 -0
  66. package/dist/_dts/theme-core/src/inputs/types.d.ts +3 -3
  67. package/dist/_dts/theme-core/src/interactive/baseSchema.d.ts +1 -1
  68. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +156 -156
  69. package/dist/_dts/theme-core/src/navigation/types.d.ts +1 -1
  70. package/dist/_dts/theme-core/src/palette/variants/types.d.ts +4 -4
  71. package/dist/_dts/theme-core/src/schema.d.ts +178 -178
  72. package/dist/_dts/theme-core/src/shared/componentStyles.d.ts +2 -2
  73. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +8 -8
  74. package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +5 -5
  75. package/dist/_dts/theme-core/src/tokens/themeColorTokens.d.ts +2 -2
  76. package/dist/{chunk-G4NLITC2.mjs → chunk-BGMNDF6T.mjs} +7 -7
  77. package/dist/{chunk-3YNCQG5N.mjs → chunk-GFE74WRD.mjs} +457 -98
  78. package/dist/{chunk-HV745JYE.mjs → chunk-RWXQBRQZ.mjs} +1 -1
  79. package/dist/{chunk-7RX72SK2.mjs → chunk-VX7B5VAC.mjs} +36 -25
  80. package/dist/{chunk-YRJKZEQ3.mjs → chunk-Z7JLNTJR.mjs} +1 -1
  81. package/dist/{chunk-KWQPAJYS.mjs → chunk-ZSHFGFUG.mjs} +2 -2
  82. package/dist/cli/index.mjs +3498 -3826
  83. package/dist/client/accordion-T2UD2ZTH.mjs +120 -0
  84. package/dist/client/altcha-LIGLPZS5.mjs +2755 -0
  85. package/dist/client/bookingForm-GUP75FKN.mjs +3022 -0
  86. package/dist/client/bookings.mjs +32 -10
  87. package/dist/client/carousel-2NQA6V57.mjs +47 -0
  88. package/dist/client/cart-5TFIAFDS.mjs +18 -0
  89. package/dist/client/checkout-TP4WAHZE.mjs +18 -0
  90. package/dist/client/chunk-2PHQZ666.mjs +383 -0
  91. package/dist/client/chunk-3DCJ45Z2.mjs +36 -0
  92. package/dist/client/chunk-3PEDD5KJ.mjs +146 -0
  93. package/dist/client/chunk-CY2ZTSE5.mjs +204 -0
  94. package/dist/client/chunk-F7LHOKEH.mjs +146 -0
  95. package/dist/client/chunk-FU2QRZMX.mjs +7617 -0
  96. package/dist/client/chunk-I2MMZO53.mjs +6 -0
  97. package/dist/client/chunk-II7O4XN3.mjs +2206 -0
  98. package/dist/client/chunk-M3L3KYYR.mjs +1181 -0
  99. package/dist/client/chunk-N7PBWBWQ.mjs +26 -0
  100. package/dist/client/chunk-NWOUL6JY.mjs +1378 -0
  101. package/dist/client/chunk-OJPZEZU2.mjs +31 -0
  102. package/dist/client/chunk-PMAIXMAB.mjs +1960 -0
  103. package/dist/client/chunk-RTZ3JKKX.mjs +178 -0
  104. package/dist/client/chunk-TQ5MSPDX.mjs +74 -0
  105. package/dist/client/chunk-VUAOE3AG.mjs +5592 -0
  106. package/dist/client/chunk-WJXXNDSE.mjs +245 -0
  107. package/dist/client/chunk-YLMKMCSJ.mjs +86 -0
  108. package/dist/client/chunk-ZLMS67RR.mjs +106 -0
  109. package/dist/client/client.mjs +51865 -52071
  110. package/dist/client/courseRegistration-5DPU6PDY.mjs +1737 -0
  111. package/dist/client/eventCalendar-F3GZGTEF.mjs +40 -0
  112. package/dist/client/eventCombined-BQOB6QMY.mjs +37 -0
  113. package/dist/client/eventDetails-PEM2EJWE.mjs +25 -0
  114. package/dist/client/eventListing-YMIQPVME.mjs +40 -0
  115. package/dist/client/eventRegistration-4I6ZNDUF.mjs +40 -0
  116. package/dist/client/form-ZJVAYZ6G.mjs +300 -0
  117. package/dist/client/headerSection-TOT5MGTF.mjs +729 -0
  118. package/dist/client/hooks.mjs +91 -42
  119. package/dist/client/locationMap-J66NZREU.mjs +227 -0
  120. package/dist/client/newsletterForm-ET2XUMRO.mjs +208 -0
  121. package/dist/client/productDetail-C2XIH7C5.mjs +18 -0
  122. package/dist/client/productList-T2AZLFTP.mjs +18 -0
  123. package/dist/client/rendering/client.mjs +12796 -18295
  124. package/dist/client/rendering/islands.mjs +108 -44022
  125. package/dist/client/rendering.mjs +45141 -45346
  126. package/dist/client/shop-UZ6IUZXD.mjs +20 -0
  127. package/dist/preview-next/before-render.mjs +37 -25
  128. package/dist/preview-next/client/runtime.mjs +5 -5
  129. package/dist/preview-next/middleware.mjs +37 -25
  130. package/dist/{sdk-runtime-NGDWSWOS.mjs → sdk-runtime-JCUQM63Y.mjs} +6 -6
  131. package/dist/server/components.mjs +472 -276
  132. package/dist/server/config-validation.mjs +91 -42
  133. package/dist/server/config.mjs +91 -42
  134. package/dist/server/data.mjs +91 -42
  135. package/dist/server/index.mjs +45 -33
  136. package/dist/server/next.mjs +468 -272
  137. package/dist/server/page-converter.mjs +41 -17
  138. package/dist/server/prebuild.mjs +1 -1
  139. package/dist/server/rendering/server.mjs +556 -275
  140. package/dist/server/rendering.mjs +474 -278
  141. package/dist/server/routing.mjs +99 -43
  142. package/dist/server/server.mjs +92 -43
  143. package/dist/server/theme-bridge.mjs +29 -0
  144. package/dist/styles/index.css +16 -0
  145. package/package.json +2 -1
  146. package/dist/SdkPreviewModeRuntime-FO6V7YVY.mjs +0 -8
@@ -0,0 +1,1737 @@
1
+ "use client";
2
+ import { DiscountCodeSection } from './chunk-OJPZEZU2.mjs';
3
+ import { useWaitlistClaim, MagicLinkForm } from './chunk-CY2ZTSE5.mjs';
4
+ import { SpinnerNode } from './chunk-TQ5MSPDX.mjs';
5
+ import { getStepNumber, logoutPortal, StepIndicator, LoggedInAsUser, CheckIcon, formatPrice, TerminalPanel, StateIcon, ClockIcon, CrossIcon, scrollToBlock, buildCheckoutReturnUrls, MembershipHero, validateFormData } from './chunk-2PHQZ666.mjs';
6
+ import { getFieldValidationControlProps, FieldValidationMessage } from './chunk-3DCJ45Z2.mjs';
7
+ import { ModalShell } from './chunk-3PEDD5KJ.mjs';
8
+ import { AltchaWidget, getChallengeUrl, isSpamProtectionEnabled } from './chunk-YLMKMCSJ.mjs';
9
+ import { BlockApiProvider, useBlockApi } from './chunk-M3L3KYYR.mjs';
10
+ import { mountReactIsland } from './chunk-N7PBWBWQ.mjs';
11
+ import { defineIslandPropsDecoder, themeButtonClassName, resolveCoursePricingState, formatCoursePriceDisplay, formatCoursePriceAmount } from './chunk-FU2QRZMX.mjs';
12
+ import { formatBookingDate, toScheduledBookingDateTime, toBookingTimeZone, formatBookingTime } from './chunk-PMAIXMAB.mjs';
13
+ import React2, { Component, useMemo, useState, useEffect } from 'react';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+
16
+ // ../blocks/src/system/runtime/nodes/course-registration/utils.ts
17
+ function formatDate(dateString, options) {
18
+ return formatBookingDate(toScheduledBookingDateTime({
19
+ startsAt: dateString,
20
+ locale: options?.locale,
21
+ timeZone: toBookingTimeZone(options?.timeZone)
22
+ }), {
23
+ weekday: "short",
24
+ month: "short",
25
+ day: "numeric",
26
+ year: "numeric"
27
+ });
28
+ }
29
+ function formatTime(dateString, options) {
30
+ return formatBookingTime(toScheduledBookingDateTime({
31
+ startsAt: dateString,
32
+ locale: options?.locale,
33
+ timeZone: toBookingTimeZone(options?.timeZone)
34
+ }), {
35
+ hour: "numeric",
36
+ minute: "2-digit"
37
+ });
38
+ }
39
+ var COURSE_REGISTRATION_ANCHOR_ID = "course-registration";
40
+
41
+ // ../blocks/src/system/runtime/nodes/course-registration/wizardSteps.ts
42
+ var WIZARD_STEPS = [
43
+ "select-course",
44
+ "enter-details",
45
+ "booking-mode",
46
+ "select-payment"
47
+ ];
48
+ var STEP_LABELS = {
49
+ "select-course": "Course",
50
+ "enter-details": "Details",
51
+ "booking-mode": "Method",
52
+ "select-payment": "Payment",
53
+ submitting: "Booking",
54
+ redirecting: "Redirecting",
55
+ "verifying-payment": "Verifying",
56
+ "verifying-timeout": "Timeout",
57
+ "payment-failed": "Failed",
58
+ success: "Complete",
59
+ waitlist: "Waitlist",
60
+ error: "Error"
61
+ };
62
+ function getWizardStepsForContext(hasPreSelectedCourse, requiresPayment, hasEligibleMemberships, bookingMode, isMembershipFastTrack) {
63
+ if (isMembershipFastTrack) {
64
+ return hasPreSelectedCourse ? ["enter-details"] : ["select-course", "enter-details"];
65
+ }
66
+ if (!requiresPayment) {
67
+ return hasPreSelectedCourse ? ["enter-details"] : ["select-course", "enter-details"];
68
+ }
69
+ if (hasEligibleMemberships) {
70
+ const skipPayment = bookingMode === "membership";
71
+ if (hasPreSelectedCourse) {
72
+ return skipPayment ? ["enter-details", "booking-mode"] : ["enter-details", "booking-mode", "select-payment"];
73
+ }
74
+ return skipPayment ? ["select-course", "enter-details", "booking-mode"] : ["select-course", "enter-details", "booking-mode", "select-payment"];
75
+ }
76
+ return hasPreSelectedCourse ? ["enter-details", "select-payment"] : ["select-course", "enter-details", "select-payment"];
77
+ }
78
+ function getStepLabels(steps) {
79
+ return steps.map((step) => STEP_LABELS[step] ?? step);
80
+ }
81
+ function StepIndicator2(props) {
82
+ return /* @__PURE__ */ jsx(StepIndicator, { ...props, classPrefix: "cr" });
83
+ }
84
+ function MembershipHero2(props) {
85
+ return /* @__PURE__ */ jsx(MembershipHero, { ...props, classPrefix: "cr" });
86
+ }
87
+ function BookingModeStep({
88
+ bookingMode,
89
+ onBookingModeChange,
90
+ eligibleMemberships,
91
+ selectedMembershipId,
92
+ onMembershipSelect,
93
+ priceCents,
94
+ currency,
95
+ showEnrollForOtherLink = false,
96
+ onEnrollForOther
97
+ }) {
98
+ const hasSingleMembership = eligibleMemberships.length === 1;
99
+ const hasMultipleMemberships = eligibleMemberships.length > 1;
100
+ const showMembershipHero = hasSingleMembership && bookingMode === "membership" && eligibleMemberships[0];
101
+ const singleMembership = eligibleMemberships[0];
102
+ return /* @__PURE__ */ jsxs("div", { className: "cr-booking-mode", children: [
103
+ /* @__PURE__ */ jsx("h2", { className: "cr-booking-mode__title", children: "How would you like to enroll?" }),
104
+ /* @__PURE__ */ jsx("p", { className: "cr-booking-mode__subtitle", children: "Choose how you want to book" }),
105
+ showMembershipHero && singleMembership && /* @__PURE__ */ jsx(MembershipHero2, { membershipName: singleMembership.membershipName }),
106
+ /* @__PURE__ */ jsxs("div", { className: "cr-payment-tiles", children: [
107
+ hasSingleMembership && singleMembership && /* @__PURE__ */ jsxs(
108
+ "button",
109
+ {
110
+ type: "button",
111
+ className: `cr-payment-tile${bookingMode === "membership" ? " cr-payment-tile--selected" : ""}`,
112
+ onClick: () => {
113
+ onBookingModeChange("membership");
114
+ onMembershipSelect(singleMembership.customerMembershipId);
115
+ },
116
+ "aria-pressed": bookingMode === "membership",
117
+ children: [
118
+ /* @__PURE__ */ jsx("div", { className: "cr-payment-tile__check", children: /* @__PURE__ */ jsx(CheckIcon, { strokeWidth: 3 }) }),
119
+ /* @__PURE__ */ jsxs("div", { className: "cr-payment-tile__header", children: [
120
+ /* @__PURE__ */ jsx("h3", { className: "cr-payment-tile__name", children: singleMembership.membershipName }),
121
+ /* @__PURE__ */ jsx("span", { className: "cr-payment-tile__price", children: "Free" })
122
+ ] }),
123
+ /* @__PURE__ */ jsx("p", { className: "cr-payment-tile__description", children: "Use your membership to book for free" })
124
+ ]
125
+ }
126
+ ),
127
+ hasMultipleMemberships && eligibleMemberships.map((membership) => /* @__PURE__ */ jsxs(
128
+ "button",
129
+ {
130
+ type: "button",
131
+ className: `cr-payment-tile${bookingMode === "membership" && selectedMembershipId === membership.customerMembershipId ? " cr-payment-tile--selected" : ""}`,
132
+ onClick: () => {
133
+ onBookingModeChange("membership");
134
+ onMembershipSelect(membership.customerMembershipId);
135
+ },
136
+ "aria-pressed": bookingMode === "membership" && selectedMembershipId === membership.customerMembershipId,
137
+ children: [
138
+ /* @__PURE__ */ jsx("div", { className: "cr-payment-tile__check", children: /* @__PURE__ */ jsx(CheckIcon, { strokeWidth: 3 }) }),
139
+ /* @__PURE__ */ jsxs("div", { className: "cr-payment-tile__header", children: [
140
+ /* @__PURE__ */ jsx("h3", { className: "cr-payment-tile__name", children: membership.membershipName }),
141
+ /* @__PURE__ */ jsx("span", { className: "cr-payment-tile__price", children: "Free" })
142
+ ] }),
143
+ /* @__PURE__ */ jsx("p", { className: "cr-payment-tile__description", children: "Use this membership to book for free" })
144
+ ]
145
+ },
146
+ membership.customerMembershipId
147
+ )),
148
+ /* @__PURE__ */ jsxs(
149
+ "button",
150
+ {
151
+ type: "button",
152
+ className: `cr-payment-tile${bookingMode === "card" ? " cr-payment-tile--selected" : ""}`,
153
+ onClick: () => {
154
+ onBookingModeChange("card");
155
+ onMembershipSelect(null);
156
+ },
157
+ "aria-pressed": bookingMode === "card",
158
+ children: [
159
+ /* @__PURE__ */ jsx("div", { className: "cr-payment-tile__check", children: /* @__PURE__ */ jsx(CheckIcon, { strokeWidth: 3 }) }),
160
+ /* @__PURE__ */ jsxs("div", { className: "cr-payment-tile__header", children: [
161
+ /* @__PURE__ */ jsx("h3", { className: "cr-payment-tile__name", children: "Pay by Card" }),
162
+ priceCents !== null && /* @__PURE__ */ jsx("span", { className: "cr-payment-tile__price", children: formatPrice(priceCents, currency) })
163
+ ] }),
164
+ /* @__PURE__ */ jsx("p", { className: "cr-payment-tile__description", children: "Pay with credit or debit card" })
165
+ ]
166
+ }
167
+ )
168
+ ] }),
169
+ showEnrollForOtherLink && onEnrollForOther && /* @__PURE__ */ jsx("div", { className: "cr-booking-mode__link", children: /* @__PURE__ */ jsx("a", { onClick: onEnrollForOther, role: "button", tabIndex: 0, children: "Enrolling for someone else?" }) })
170
+ ] });
171
+ }
172
+ var PORTAL_AUTH_COPY = {
173
+ membership: {
174
+ title: "Have a membership?",
175
+ description: "Log in to use your membership to book for free",
176
+ buttonText: "Log in with email",
177
+ modalTitle: "Log in",
178
+ modalHeading: "Log in to your account",
179
+ modalSubtitle: "Enter your email to receive a magic link",
180
+ successMessage: "Check your email for a login link. After clicking, refresh this page."
181
+ },
182
+ "waitlist-claim": {
183
+ title: "Log in to claim your waitlist spot",
184
+ description: "",
185
+ buttonText: "Log in with email to continue",
186
+ modalTitle: "Log in to continue",
187
+ modalHeading: "Log in to your account to continue",
188
+ modalSubtitle: "Enter your email to receive a login link and continue your waitlist claim",
189
+ successMessage: "Check your email for a login link. After clicking it, return here to continue your claim."
190
+ }
191
+ };
192
+ function PortalAuthSidebar({
193
+ onMagicLinkSent,
194
+ autoOpen = false,
195
+ variant = "membership",
196
+ loginMessage
197
+ }) {
198
+ const [showModal, setShowModal] = useState(false);
199
+ const copy = PORTAL_AUTH_COPY[variant];
200
+ const description = variant === "waitlist-claim" ? loginMessage ?? "Log in to continue with your waitlist claim." : copy.description;
201
+ useEffect(() => {
202
+ if (!autoOpen) return;
203
+ setShowModal(true);
204
+ }, [autoOpen]);
205
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
206
+ /* @__PURE__ */ jsxs("div", { className: "cr-portal-sidebar", children: [
207
+ /* @__PURE__ */ jsx("div", { className: "cr-portal-sidebar__icon", "aria-hidden": "true", children: "\u{1F464}" }),
208
+ /* @__PURE__ */ jsx("h3", { className: "cr-portal-sidebar__title", children: copy.title }),
209
+ /* @__PURE__ */ jsx("p", { className: "cr-portal-sidebar__text", children: description }),
210
+ /* @__PURE__ */ jsx("div", { className: "cr-portal-sidebar__action", children: /* @__PURE__ */ jsx(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ className: "cr-portal-sidebar__link",
215
+ onClick: () => setShowModal(true),
216
+ children: copy.buttonText
217
+ }
218
+ ) })
219
+ ] }),
220
+ showModal && /* @__PURE__ */ jsx(
221
+ ModalShell,
222
+ {
223
+ title: copy.modalTitle,
224
+ titleId: "cr-portal-auth-modal-title",
225
+ onClose: () => setShowModal(false),
226
+ overlayClassName: "cr-modal-overlay",
227
+ modalClassName: "cr-modal",
228
+ renderSrOnlyTitle: true,
229
+ renderHeader: ({ closeButtonRef, onClose }) => /* @__PURE__ */ jsx(
230
+ "button",
231
+ {
232
+ ref: closeButtonRef,
233
+ type: "button",
234
+ className: "cr-modal-close",
235
+ onClick: onClose,
236
+ "aria-label": "Close",
237
+ children: /* @__PURE__ */ jsx(
238
+ "svg",
239
+ {
240
+ width: "20",
241
+ height: "20",
242
+ viewBox: "0 0 24 24",
243
+ fill: "none",
244
+ stroke: "currentColor",
245
+ strokeWidth: "2",
246
+ "aria-hidden": "true",
247
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
248
+ }
249
+ )
250
+ }
251
+ ),
252
+ children: /* @__PURE__ */ jsx(
253
+ MagicLinkForm,
254
+ {
255
+ classPrefix: "cr",
256
+ title: copy.modalHeading,
257
+ subtitle: copy.modalSubtitle,
258
+ buttonText: "Send login link",
259
+ successMessage: copy.successMessage,
260
+ onSuccess: () => {
261
+ onMagicLinkSent?.();
262
+ }
263
+ }
264
+ )
265
+ }
266
+ )
267
+ ] });
268
+ }
269
+ function SuccessStep({
270
+ usedMembership,
271
+ successMessage,
272
+ membershipMessage
273
+ }) {
274
+ return /* @__PURE__ */ jsx(
275
+ TerminalPanel,
276
+ {
277
+ tone: "success",
278
+ className: "cr-success",
279
+ titleClassName: "cr-success__title",
280
+ icon: /* @__PURE__ */ jsx(StateIcon, { variant: "success", children: /* @__PURE__ */ jsx(CheckIcon, { strokeWidth: 2 }) }),
281
+ title: "Enrollment Complete!",
282
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [
283
+ /* @__PURE__ */ jsx("p", { className: "cr-success__message", children: usedMembership ? membershipMessage : successMessage }),
284
+ usedMembership && /* @__PURE__ */ jsx("p", { className: "cr-success__membership", children: "Enrolled using your membership" })
285
+ ] })
286
+ }
287
+ );
288
+ }
289
+ function ErrorStep({
290
+ message,
291
+ buttonText = "Try Again",
292
+ buttonVariant = "primary",
293
+ onRetry
294
+ }) {
295
+ const buttonClass = themeButtonClassName({ variant: buttonVariant, size: "md" });
296
+ return /* @__PURE__ */ jsx(
297
+ TerminalPanel,
298
+ {
299
+ tone: "error",
300
+ className: "cr-error",
301
+ titleClassName: "cr-error__title",
302
+ icon: /* @__PURE__ */ jsx(StateIcon, { variant: "error", children: /* @__PURE__ */ jsx(CrossIcon, {}) }),
303
+ title: "Enrollment Failed",
304
+ body: /* @__PURE__ */ jsx("p", { className: "cr-error__message", children: message }),
305
+ actionsClassName: "cr-error__retry",
306
+ actions: /* @__PURE__ */ jsx("button", { type: "button", onClick: onRetry, className: buttonClass, children: buttonText })
307
+ }
308
+ );
309
+ }
310
+ function VerifyingTimeoutStep({
311
+ buttonText = "Check Again",
312
+ buttonVariant = "primary",
313
+ onCheckAgain,
314
+ supportEmail
315
+ }) {
316
+ const buttonClass = themeButtonClassName({ variant: buttonVariant, size: "md" });
317
+ return /* @__PURE__ */ jsx(
318
+ TerminalPanel,
319
+ {
320
+ tone: "warning",
321
+ className: "cr-error",
322
+ titleClassName: "cr-error__title",
323
+ icon: /* @__PURE__ */ jsx(StateIcon, { variant: "warning", children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
324
+ title: "Payment Verification Taking Longer",
325
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [
326
+ /* @__PURE__ */ jsx("p", { className: "cr-error__message", children: "Your payment may still be processing. Please check your email for confirmation." }),
327
+ supportEmail && /* @__PURE__ */ jsxs("p", { className: "cr-error__message", children: [
328
+ "If you need help, contact us at",
329
+ " ",
330
+ /* @__PURE__ */ jsx("a", { href: `mailto:${supportEmail}`, children: supportEmail })
331
+ ] })
332
+ ] }),
333
+ actionsClassName: "cr-error__retry",
334
+ actions: /* @__PURE__ */ jsx("button", { type: "button", onClick: onCheckAgain, className: buttonClass, children: buttonText })
335
+ }
336
+ );
337
+ }
338
+ function PaymentFailedStep({
339
+ message = "Your payment could not be processed.",
340
+ buttonText = "Try Again",
341
+ buttonVariant = "primary",
342
+ onRetry
343
+ }) {
344
+ const buttonClass = themeButtonClassName({ variant: buttonVariant, size: "md" });
345
+ return /* @__PURE__ */ jsx(
346
+ TerminalPanel,
347
+ {
348
+ tone: "error",
349
+ className: "cr-payment-failed",
350
+ titleClassName: "cr-payment-failed__title",
351
+ icon: /* @__PURE__ */ jsx(StateIcon, { variant: "error", children: /* @__PURE__ */ jsx(CrossIcon, {}) }),
352
+ title: "Payment Failed",
353
+ body: /* @__PURE__ */ jsx("p", { className: "cr-payment-failed__message", children: message }),
354
+ actionsClassName: "cr-payment-failed__retry",
355
+ actions: /* @__PURE__ */ jsx("button", { type: "button", onClick: onRetry, className: buttonClass, children: buttonText })
356
+ }
357
+ );
358
+ }
359
+ function WaitlistStep({ message, position }) {
360
+ return /* @__PURE__ */ jsx(
361
+ TerminalPanel,
362
+ {
363
+ tone: "info",
364
+ className: "cr-waitlist",
365
+ titleClassName: "cr-waitlist__title",
366
+ icon: /* @__PURE__ */ jsx(StateIcon, { variant: "waitlist", children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
367
+ title: "Added to Waitlist",
368
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [
369
+ /* @__PURE__ */ jsx("p", { className: "cr-waitlist__message", children: message }),
370
+ typeof position === "number" && /* @__PURE__ */ jsxs("p", { className: "cr-waitlist__position", children: [
371
+ "Your position: #",
372
+ position
373
+ ] })
374
+ ] })
375
+ }
376
+ );
377
+ }
378
+ var CourseRegistrationErrorBoundary = class extends Component {
379
+ constructor(props) {
380
+ super(props);
381
+ this.state = { hasError: false };
382
+ }
383
+ static getDerivedStateFromError(error) {
384
+ return { hasError: true, error };
385
+ }
386
+ componentDidCatch(error, errorInfo) {
387
+ console.error("Course registration error:", error, errorInfo);
388
+ }
389
+ render() {
390
+ if (this.state.hasError) {
391
+ return /* @__PURE__ */ jsx(
392
+ "div",
393
+ {
394
+ id: COURSE_REGISTRATION_ANCHOR_ID,
395
+ className: `course-registration ${this.props.className || ""}`,
396
+ children: /* @__PURE__ */ jsxs("div", { className: "cr-error", children: [
397
+ /* @__PURE__ */ jsx("div", { className: "cr-error__icon", children: "\u2715" }),
398
+ /* @__PURE__ */ jsx("h3", { className: "cr-error__title", children: "Something went wrong" }),
399
+ /* @__PURE__ */ jsx("p", { className: "cr-error__message", children: "Unable to load the booking form. Please refresh the page and try again." })
400
+ ] })
401
+ }
402
+ );
403
+ }
404
+ return this.props.children;
405
+ }
406
+ };
407
+ function TeamMemberInlineList({
408
+ teamMembers
409
+ }) {
410
+ return /* @__PURE__ */ jsx(Fragment, { children: teamMembers.map((teamMember, index) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
411
+ index > 0 ? ", " : "",
412
+ teamMember.profilePath ? /* @__PURE__ */ jsx("a", { href: teamMember.profilePath, children: teamMember.name }) : teamMember.name
413
+ ] }, teamMember.id)) });
414
+ }
415
+ function CourseCard({
416
+ course,
417
+ isSelected,
418
+ onSelect,
419
+ showSessions,
420
+ showPrice,
421
+ showCapacity,
422
+ showVenue
423
+ }) {
424
+ const enrollmentClosed = course.enrollmentOpen === false;
425
+ const teamMemberNames = (course.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
426
+ const pricing = resolveCoursePricingState(course);
427
+ const priceLabel = formatCoursePriceDisplay(pricing.display);
428
+ return /* @__PURE__ */ jsxs(
429
+ "div",
430
+ {
431
+ className: `cr-course-card ${isSelected ? "cr-course-card--selected" : ""}`,
432
+ onClick: onSelect,
433
+ role: "button",
434
+ tabIndex: 0,
435
+ "aria-pressed": isSelected,
436
+ onKeyDown: (e) => {
437
+ if (e.key === "Enter" || e.key === " ") {
438
+ e.preventDefault();
439
+ onSelect();
440
+ }
441
+ },
442
+ children: [
443
+ /* @__PURE__ */ jsx("h3", { className: "cr-course-card__title", children: course.title }),
444
+ enrollmentClosed && /* @__PURE__ */ jsx("p", { className: "cr-course-card__status", children: "Registration closed" }),
445
+ showSessions && course.sessionsCount > 0 && /* @__PURE__ */ jsxs("p", { className: "cr-course-card__sessions", children: [
446
+ course.sessionsCount,
447
+ " session",
448
+ course.sessionsCount !== 1 ? "s" : ""
449
+ ] }),
450
+ showPrice && pricing.display.kind !== "unavailable" && /* @__PURE__ */ jsx("p", { className: "cr-course-card__price", children: priceLabel }),
451
+ showCapacity && course.availableSpots !== null && /* @__PURE__ */ jsx("p", { className: "cr-course-card__capacity", children: course.availableSpots > 0 ? `${course.availableSpots} spot${course.availableSpots !== 1 ? "s" : ""} available` : "Waitlist only" }),
452
+ showVenue && course.venue && /* @__PURE__ */ jsx("p", { className: "cr-course-card__venue", children: course.venue.name }),
453
+ teamMemberNames.length > 0 && /* @__PURE__ */ jsxs("p", { className: "cr-course-card__venue", children: [
454
+ "Team: ",
455
+ teamMemberNames.join(", ")
456
+ ] })
457
+ ]
458
+ }
459
+ );
460
+ }
461
+ function CourseSummary({
462
+ course,
463
+ pricing,
464
+ showSessions,
465
+ showVenue,
466
+ showPrice,
467
+ showMembershipNote
468
+ }) {
469
+ const teamMembers = course.teamMembers ?? [];
470
+ const priceLabel = pricing.selectedOption ? `${pricing.selectedOption.name} - ${formatCoursePriceAmount(
471
+ pricing.selectedOption.priceAmount,
472
+ pricing.currency
473
+ )}` : formatCoursePriceDisplay(pricing.display);
474
+ return /* @__PURE__ */ jsxs("div", { className: "cr-summary", children: [
475
+ /* @__PURE__ */ jsx("h3", { className: "cr-summary__title", children: course.title }),
476
+ showSessions && course.sessions.length > 0 && /* @__PURE__ */ jsxs("div", { className: "cr-summary__sessions", children: [
477
+ /* @__PURE__ */ jsx("h4", { className: "cr-summary__sessions-title", children: "Sessions" }),
478
+ /* @__PURE__ */ jsx("ul", { className: "cr-summary__sessions-list", children: course.sessions.map((session) => /* @__PURE__ */ jsxs("li", { className: "cr-summary__session", children: [
479
+ formatDate(session.startsAt, { timeZone: session.timeZone }),
480
+ " ",
481
+ "at",
482
+ " ",
483
+ formatTime(session.startsAt, { timeZone: session.timeZone })
484
+ ] }, session.id)) })
485
+ ] }),
486
+ showVenue && course.venue && /* @__PURE__ */ jsxs("p", { className: "cr-summary__venue", children: [
487
+ /* @__PURE__ */ jsx("strong", { children: "Venue:" }),
488
+ " ",
489
+ course.venue.name,
490
+ course.venue.address && ` - ${course.venue.address}`
491
+ ] }),
492
+ teamMembers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "cr-summary__venue", children: [
493
+ /* @__PURE__ */ jsx("strong", { children: "Team:" }),
494
+ " ",
495
+ /* @__PURE__ */ jsx(TeamMemberInlineList, { teamMembers })
496
+ ] }),
497
+ showPrice && pricing.display.kind !== "unavailable" && /* @__PURE__ */ jsxs("p", { className: "cr-summary__price", children: [
498
+ /* @__PURE__ */ jsx("strong", { children: pricing.selectableOptions.length > 0 ? "Pricing:" : "Price:" }),
499
+ " ",
500
+ priceLabel
501
+ ] }),
502
+ showMembershipNote && /* @__PURE__ */ jsx("p", { className: "cr-summary__membership", children: "\u2713 Your membership can be used to book for free" })
503
+ ] });
504
+ }
505
+ function CoursePricingSelector({
506
+ pricing,
507
+ selectedPricingOptionId,
508
+ onSelect
509
+ }) {
510
+ if (pricing.selectableOptions.length <= 1) {
511
+ return null;
512
+ }
513
+ return /* @__PURE__ */ jsxs("fieldset", { className: "cr-form__field", children: [
514
+ /* @__PURE__ */ jsx("legend", { id: "cr-pricing-options-label", className: "cr-form__label", children: "Pricing option" }),
515
+ /* @__PURE__ */ jsx(
516
+ "div",
517
+ {
518
+ className: "cr-payment-tiles",
519
+ role: "radiogroup",
520
+ "aria-labelledby": "cr-pricing-options-label",
521
+ children: pricing.selectableOptions.map((option) => {
522
+ const isSelected = selectedPricingOptionId === option.id;
523
+ const priceLabel = formatCoursePriceAmount(
524
+ option.priceAmount,
525
+ pricing.currency
526
+ );
527
+ return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
528
+ "button",
529
+ {
530
+ type: "button",
531
+ role: "radio",
532
+ "aria-checked": isSelected,
533
+ className: `cr-payment-tile${isSelected ? " cr-payment-tile--selected" : ""}`,
534
+ onClick: () => onSelect(option.id),
535
+ children: [
536
+ /* @__PURE__ */ jsxs("div", { className: "cr-payment-tile__header", children: [
537
+ /* @__PURE__ */ jsx("h3", { className: "cr-payment-tile__name", children: option.name }),
538
+ /* @__PURE__ */ jsx("span", { className: "cr-payment-tile__price", children: priceLabel })
539
+ ] }),
540
+ option.description ? /* @__PURE__ */ jsx("p", { className: "cr-payment-tile__description", children: option.description }) : null
541
+ ]
542
+ }
543
+ ) }, option.id);
544
+ })
545
+ }
546
+ )
547
+ ] });
548
+ }
549
+ function EnrollmentForm({ formData, errors, onChange, isEmailLocked, isLoading, onLogout }) {
550
+ const nameErrorId = "cr-name-error";
551
+ const emailErrorId = "cr-email-error";
552
+ const nameValidationProps = getFieldValidationControlProps({
553
+ message: errors.name,
554
+ messageId: nameErrorId
555
+ });
556
+ const emailValidationProps = getFieldValidationControlProps({
557
+ message: errors.email,
558
+ messageId: emailErrorId
559
+ });
560
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
561
+ /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
562
+ /* @__PURE__ */ jsx("label", { htmlFor: "cr-name", className: "cr-form__label", children: "Name *" }),
563
+ /* @__PURE__ */ jsx(
564
+ "input",
565
+ {
566
+ id: "cr-name",
567
+ type: "text",
568
+ className: `cr-form__input ${errors.name ? "cr-form__input--error" : ""}`,
569
+ value: formData.name,
570
+ onChange: (e) => onChange({ ...formData, name: e.target.value }),
571
+ disabled: isLoading,
572
+ ...nameValidationProps
573
+ }
574
+ ),
575
+ errors.name && /* @__PURE__ */ jsx(FieldValidationMessage, { id: nameErrorId, className: "cr-form__error", children: errors.name })
576
+ ] }),
577
+ /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
578
+ /* @__PURE__ */ jsx("label", { htmlFor: "cr-email", className: "cr-form__label", children: "Email *" }),
579
+ /* @__PURE__ */ jsx(
580
+ "input",
581
+ {
582
+ id: "cr-email",
583
+ type: "email",
584
+ className: `cr-form__input ${errors.email ? "cr-form__input--error" : ""}`,
585
+ value: formData.email,
586
+ onChange: (e) => onChange({ ...formData, email: e.target.value }),
587
+ disabled: isEmailLocked || isLoading,
588
+ ...emailValidationProps
589
+ }
590
+ ),
591
+ errors.email && /* @__PURE__ */ jsx(FieldValidationMessage, { id: emailErrorId, className: "cr-form__error", children: errors.email }),
592
+ isEmailLocked && /* @__PURE__ */ jsx(
593
+ LoggedInAsUser,
594
+ {
595
+ email: formData.email,
596
+ onLogout,
597
+ classPrefix: "cr"
598
+ }
599
+ )
600
+ ] }),
601
+ /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
602
+ /* @__PURE__ */ jsx("label", { htmlFor: "cr-phone", className: "cr-form__label", children: "Phone" }),
603
+ /* @__PURE__ */ jsx(
604
+ "input",
605
+ {
606
+ id: "cr-phone",
607
+ type: "tel",
608
+ className: "cr-form__input",
609
+ value: formData.phone,
610
+ onChange: (e) => onChange({ ...formData, phone: e.target.value }),
611
+ disabled: isLoading
612
+ }
613
+ )
614
+ ] }),
615
+ /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
616
+ /* @__PURE__ */ jsx("label", { htmlFor: "cr-notes", className: "cr-form__label", children: "Notes" }),
617
+ /* @__PURE__ */ jsx(
618
+ "textarea",
619
+ {
620
+ id: "cr-notes",
621
+ className: "cr-form__textarea",
622
+ value: formData.notes,
623
+ onChange: (e) => onChange({ ...formData, notes: e.target.value }),
624
+ rows: 3,
625
+ disabled: isLoading
626
+ }
627
+ )
628
+ ] })
629
+ ] });
630
+ }
631
+ function shouldFetchCourseEligibility(course, hasPaidSelection, portalAuthState, hasWaitlistClaimToken) {
632
+ if (!course || portalAuthState === "checking") return false;
633
+ if (hasWaitlistClaimToken) return false;
634
+ const needsMembershipEligibility = portalAuthState === "authenticated" && hasPaidSelection && course.acceptsMemberships;
635
+ const needsWaitlistAvailabilityCheck = course.availableSpots !== null && course.availableSpots <= 0;
636
+ return needsMembershipEligibility || needsWaitlistAvailabilityCheck;
637
+ }
638
+ function getSessionIdFromUrl() {
639
+ const params = new URLSearchParams(window.location.search);
640
+ const fromSearch = params.get("session_id");
641
+ if (fromSearch) return fromSearch;
642
+ const hash = window.location.hash;
643
+ const sessionIdMatch = hash.match(/[?&]session_id=([^&]+)/);
644
+ return sessionIdMatch?.[1] ?? null;
645
+ }
646
+ function getMissingCoursePricingSelectionMessage(pricing) {
647
+ if (pricing?.requiresSelection && !pricing.selectedOption) {
648
+ return "Choose a pricing option to continue.";
649
+ }
650
+ return null;
651
+ }
652
+ function buildCoursePricingOptionPayload(pricing) {
653
+ return pricing?.selectedOption ? { pricingOptionId: pricing.selectedOption.id } : {};
654
+ }
655
+ function resolveHasCoursePayment(pricing) {
656
+ if (!pricing) {
657
+ return false;
658
+ }
659
+ if (pricing.selectedOption) {
660
+ return pricing.selectedRequiresPayment;
661
+ }
662
+ return pricing.hasPaidSelection;
663
+ }
664
+ function useCourseRegistrationWizard(props) {
665
+ const api = useBlockApi();
666
+ const { contentEntry, courses, entrySubrouteContext } = props;
667
+ const scopedCourses = useMemo(() => {
668
+ const allCourses = courses ?? [];
669
+ if (entrySubrouteContext?.kind === "course-run") {
670
+ const matched = allCourses.find((c) => c.id === entrySubrouteContext.run.courseId) ?? null;
671
+ if (matched) return [matched];
672
+ }
673
+ if (contentEntry?.id) {
674
+ const matchedCourses = allCourses.filter((c) => c.contentEntryId === contentEntry.id);
675
+ if (matchedCourses.length > 0) return matchedCourses;
676
+ }
677
+ if (contentEntry?.slug) {
678
+ const matchedCourse = allCourses.find((c) => c.slug === contentEntry.slug);
679
+ if (matchedCourse) return [matchedCourse];
680
+ }
681
+ return allCourses;
682
+ }, [courses, contentEntry?.id, contentEntry?.slug, entrySubrouteContext]);
683
+ const [step, setStep] = useState(() => {
684
+ const params = new URLSearchParams(window.location.search);
685
+ const enrollment = params.get("enrollment");
686
+ const sessionId = getSessionIdFromUrl();
687
+ if (enrollment === "success" && sessionId) return "verifying-payment";
688
+ return scopedCourses.length === 1 ? "enter-details" : "select-course";
689
+ });
690
+ const [selectedCourse, setSelectedCourse] = useState(
691
+ scopedCourses.length === 1 ? scopedCourses[0] ?? null : null
692
+ );
693
+ const [selectedPricingOptionId, setSelectedPricingOptionId] = useState(null);
694
+ const [formData, setFormData] = useState({
695
+ name: "",
696
+ email: "",
697
+ phone: "",
698
+ notes: ""
699
+ });
700
+ const [errors, setErrors] = useState({});
701
+ const [enrollmentResult, setEnrollmentResult] = useState(null);
702
+ const [errorMessage, setErrorMessage] = useState("");
703
+ const [portalAuthState, setPortalAuthState] = useState("checking");
704
+ const [altchaSolution, setAltchaSolution] = useState(null);
705
+ const showSpamProtection = isSpamProtectionEnabled();
706
+ const [eligibilityLoading, setEligibilityLoading] = useState(false);
707
+ const [eligibilityError, setEligibilityError] = useState(null);
708
+ const [courseEligibility, setCourseEligibility] = useState(null);
709
+ const [eligibleMemberships, setEligibleMemberships] = useState([]);
710
+ const [paymentChoice, setPaymentChoice] = useState("checkout");
711
+ const [selectedMembershipId, setSelectedMembershipId] = useState(null);
712
+ const [discountUiOpen, setDiscountUiOpen] = useState(false);
713
+ const [discountCode, setDiscountCode] = useState("");
714
+ const [discountsAvailable, setDiscountsAvailable] = useState(false);
715
+ const [manualCodeEntryAllowed, setManualCodeEntryAllowed] = useState(false);
716
+ const [discountsAvailableLoading, setDiscountsAvailableLoading] = useState(false);
717
+ const [bookingMode, setBookingMode] = useState(null);
718
+ const waitlistClaim = useWaitlistClaim();
719
+ const selectedCoursePricing = useMemo(
720
+ () => selectedCourse ? resolveCoursePricingState(selectedCourse, selectedPricingOptionId) : null,
721
+ [selectedCourse, selectedPricingOptionId]
722
+ );
723
+ const missingPricingSelectionMessage = getMissingCoursePricingSelectionMessage(
724
+ selectedCoursePricing
725
+ );
726
+ const selectedCoursePricingPayload = buildCoursePricingOptionPayload(
727
+ selectedCoursePricing
728
+ );
729
+ const showSoldOutCourseError = () => {
730
+ setErrorMessage(courseEligibility?.reason ?? "This course is fully booked.");
731
+ setStep("error");
732
+ return true;
733
+ };
734
+ useEffect(() => {
735
+ let cancelled = false;
736
+ async function checkPortalAuth() {
737
+ const hasPortalAuth = api.config.auth.type === "bearer" || api.config.authOverrides?.portal?.type === "bearer";
738
+ if (!hasPortalAuth) {
739
+ setPortalAuthState("unauthenticated");
740
+ return;
741
+ }
742
+ setPortalAuthState("checking");
743
+ try {
744
+ const me = await api.getPortalProfile();
745
+ if (cancelled) return;
746
+ setPortalAuthState("authenticated");
747
+ setFormData((prev) => ({
748
+ ...prev,
749
+ email: me.profile.email,
750
+ name: prev.name || me.profile.name,
751
+ phone: prev.phone || me.profile.phone || ""
752
+ }));
753
+ } catch {
754
+ if (cancelled) return;
755
+ setPortalAuthState("unauthenticated");
756
+ }
757
+ }
758
+ checkPortalAuth();
759
+ return () => {
760
+ cancelled = true;
761
+ };
762
+ }, [api]);
763
+ useEffect(() => {
764
+ const claim = waitlistClaim.claim;
765
+ if (!claim || claim.subject.kind !== "course") return;
766
+ const courseId = claim.subject.courseId;
767
+ const matchedCourse = scopedCourses.find((course) => course.id === courseId) ?? null;
768
+ if (!matchedCourse) {
769
+ setErrorMessage("This waitlist claim is for a different course.");
770
+ setStep("error");
771
+ return;
772
+ }
773
+ setSelectedCourse(matchedCourse);
774
+ setFormData((prev) => ({
775
+ ...prev,
776
+ email: claim.prefill.email,
777
+ name: claim.prefill.name,
778
+ phone: claim.prefill.phone ?? "",
779
+ notes: claim.prefill.notes ?? ""
780
+ }));
781
+ if (step === "select-course") {
782
+ setStep("enter-details");
783
+ }
784
+ }, [waitlistClaim.claim, scopedCourses, step]);
785
+ useEffect(() => {
786
+ if (waitlistClaim.requiresPortalLogin) return;
787
+ if (!waitlistClaim.error) return;
788
+ setErrorMessage(waitlistClaim.error);
789
+ setStep("error");
790
+ }, [waitlistClaim.error, waitlistClaim.requiresPortalLogin]);
791
+ useEffect(() => {
792
+ if (!selectedCourse) {
793
+ setSelectedPricingOptionId(null);
794
+ return;
795
+ }
796
+ const defaultPricing = resolveCoursePricingState(selectedCourse, null);
797
+ setSelectedPricingOptionId((previous) => {
798
+ if (previous && defaultPricing.selectableOptions.some((option) => option.id === previous)) {
799
+ return previous;
800
+ }
801
+ return defaultPricing.selectedOption?.id ?? null;
802
+ });
803
+ }, [selectedCourse]);
804
+ useEffect(() => {
805
+ let cancelled = false;
806
+ async function fetchEligibility() {
807
+ setCourseEligibility(null);
808
+ setEligibleMemberships([]);
809
+ setSelectedMembershipId(null);
810
+ setEligibilityError(null);
811
+ setPaymentChoice("checkout");
812
+ const course = selectedCourse;
813
+ if (!shouldFetchCourseEligibility(
814
+ course,
815
+ selectedCoursePricing?.hasPaidSelection ?? false,
816
+ portalAuthState,
817
+ waitlistClaim.token !== null
818
+ )) {
819
+ return;
820
+ }
821
+ if (!course) return;
822
+ setEligibilityLoading(true);
823
+ try {
824
+ const res = await api.checkCourseEligibility({ courseId: course.id });
825
+ if (cancelled) return;
826
+ setCourseEligibility(res);
827
+ const memberships = res.eligibleMemberships ?? [];
828
+ setEligibleMemberships(memberships);
829
+ if (portalAuthState === "authenticated" && memberships.length > 0) {
830
+ setPaymentChoice("membership");
831
+ if (memberships.length === 1) {
832
+ setSelectedMembershipId(memberships[0]?.customerMembershipId ?? null);
833
+ }
834
+ }
835
+ } catch (error) {
836
+ if (cancelled) return;
837
+ const message = error instanceof Error ? error.message : "Failed to check membership eligibility";
838
+ setEligibilityError(message);
839
+ } finally {
840
+ if (!cancelled) setEligibilityLoading(false);
841
+ }
842
+ }
843
+ fetchEligibility();
844
+ return () => {
845
+ cancelled = true;
846
+ };
847
+ }, [
848
+ api,
849
+ selectedCourse,
850
+ selectedCoursePricing?.hasPaidSelection,
851
+ portalAuthState,
852
+ waitlistClaim.token
853
+ ]);
854
+ useEffect(() => {
855
+ let cancelled = false;
856
+ async function verifyAfterCheckout() {
857
+ const params = new URLSearchParams(window.location.search);
858
+ const enrollment = params.get("enrollment");
859
+ const sessionId = getSessionIdFromUrl();
860
+ if (enrollment === "cancelled") {
861
+ scrollToBlock(COURSE_REGISTRATION_ANCHOR_ID);
862
+ setErrorMessage("Payment was cancelled. You can try again.");
863
+ setStep("error");
864
+ return;
865
+ }
866
+ if (enrollment !== "success" || !sessionId) return;
867
+ scrollToBlock(COURSE_REGISTRATION_ANCHOR_ID);
868
+ setStep("verifying-payment");
869
+ const startedAt = Date.now();
870
+ const timeoutMs = 3e4;
871
+ while (!cancelled) {
872
+ try {
873
+ const status = await api.getCourseRegistrationStatus({ sessionId });
874
+ if (cancelled) return;
875
+ if (status.status === "enrolled" || status.status === "completed") {
876
+ setEnrollmentResult({ status: "enrolled", usedMembership: false });
877
+ setStep("success");
878
+ return;
879
+ }
880
+ if (status.status === "waitlist") {
881
+ setEnrollmentResult({ status: "waitlist", usedMembership: false });
882
+ setStep("waitlist");
883
+ return;
884
+ }
885
+ if (status.status === "cancelled") {
886
+ setErrorMessage("Payment did not complete. Please try again.");
887
+ setStep("error");
888
+ return;
889
+ }
890
+ } catch (error) {
891
+ const message = error instanceof Error ? error.message : "Failed to verify payment";
892
+ setErrorMessage(message);
893
+ }
894
+ if (Date.now() - startedAt > timeoutMs) {
895
+ setErrorMessage("Payment is still processing. Please check your email for confirmation.");
896
+ setStep("error");
897
+ return;
898
+ }
899
+ await new Promise((r) => setTimeout(r, 1500));
900
+ }
901
+ }
902
+ verifyAfterCheckout();
903
+ return () => {
904
+ cancelled = true;
905
+ };
906
+ }, [api]);
907
+ const handleValidateForm = () => {
908
+ const newErrors = validateFormData(formData);
909
+ setErrors(newErrors);
910
+ return Object.keys(newErrors).length === 0;
911
+ };
912
+ const handleCourseSelect = (course) => {
913
+ setSelectedCourse(course);
914
+ setStep("enter-details");
915
+ };
916
+ const handleSubmit = async () => {
917
+ if (!selectedCourse) return;
918
+ if (!handleValidateForm()) return;
919
+ if (missingPricingSelectionMessage) {
920
+ setErrorMessage(missingPricingSelectionMessage);
921
+ setStep("error");
922
+ return;
923
+ }
924
+ const requiresPayment = selectedCoursePricing?.selectedRequiresPayment ?? false;
925
+ const canUseMembership2 = requiresPayment && selectedCourse.acceptsMemberships && portalAuthState === "authenticated" && eligibleMemberships.length > 0;
926
+ if (showSpamProtection && !altchaSolution) {
927
+ setErrorMessage("Please complete the verification check.");
928
+ setStep("error");
929
+ return;
930
+ }
931
+ if (!waitlistClaim.claim && courseEligibility?.isFull && !courseEligibility.canJoinWaitlist && showSoldOutCourseError()) {
932
+ return;
933
+ }
934
+ if (courseEligibility?.canJoinWaitlist) {
935
+ setStep("submitting");
936
+ try {
937
+ const result = await api.enrollInCourse({
938
+ courseId: selectedCourse.id,
939
+ ...selectedCoursePricingPayload,
940
+ email: formData.email.trim().toLowerCase(),
941
+ name: formData.name.trim(),
942
+ phone: formData.phone.trim() || void 0,
943
+ formData: { notes: formData.notes.trim() || void 0 },
944
+ altchaSolution,
945
+ paymentMethod: "free",
946
+ claimToken: waitlistClaim.token ?? void 0
947
+ });
948
+ setEnrollmentResult({
949
+ status: result.status,
950
+ waitlistPosition: result.waitlistPosition ?? void 0,
951
+ usedMembership: false
952
+ });
953
+ setStep(result.status === "waitlist" ? "waitlist" : "success");
954
+ } catch (error) {
955
+ const message = error instanceof Error ? error.message : "Booking failed";
956
+ setErrorMessage(message);
957
+ setStep("error");
958
+ }
959
+ return;
960
+ }
961
+ if (requiresPayment && canUseMembership2) {
962
+ setStep("select-payment");
963
+ return;
964
+ }
965
+ if (requiresPayment) {
966
+ setStep("submitting");
967
+ try {
968
+ const { successUrl, cancelUrl } = buildCheckoutReturnUrls("enrollment", COURSE_REGISTRATION_ANCHOR_ID);
969
+ const checkoutResult = await api.createCourseCheckout({
970
+ courseId: selectedCourse.id,
971
+ ...selectedCoursePricingPayload,
972
+ email: formData.email.trim().toLowerCase(),
973
+ name: formData.name.trim(),
974
+ phone: formData.phone.trim() || void 0,
975
+ formData: { notes: formData.notes.trim() || void 0 },
976
+ altchaSolution,
977
+ discountCode: discountCode.trim() ? discountCode.trim() : void 0,
978
+ successUrl,
979
+ cancelUrl,
980
+ claimToken: waitlistClaim.token ?? void 0
981
+ });
982
+ if (checkoutResult.bypassed) {
983
+ setEnrollmentResult({
984
+ status: "enrolled",
985
+ waitlistPosition: void 0,
986
+ usedMembership: false
987
+ });
988
+ setStep("success");
989
+ return;
990
+ }
991
+ if (!checkoutResult.checkoutUrl) {
992
+ throw new Error("Checkout URL missing");
993
+ }
994
+ setStep("redirecting");
995
+ window.location.href = checkoutResult.checkoutUrl;
996
+ return;
997
+ } catch (error) {
998
+ const message = error instanceof Error ? error.message : "Checkout failed";
999
+ setErrorMessage(message);
1000
+ setStep("error");
1001
+ return;
1002
+ }
1003
+ }
1004
+ setStep("submitting");
1005
+ try {
1006
+ const result = await api.enrollInCourse({
1007
+ courseId: selectedCourse.id,
1008
+ ...selectedCoursePricingPayload,
1009
+ email: formData.email.trim().toLowerCase(),
1010
+ name: formData.name.trim(),
1011
+ phone: formData.phone.trim() || void 0,
1012
+ formData: { notes: formData.notes.trim() || void 0 },
1013
+ altchaSolution,
1014
+ paymentMethod: "free",
1015
+ claimToken: waitlistClaim.token ?? void 0
1016
+ });
1017
+ setEnrollmentResult({
1018
+ status: result.status,
1019
+ waitlistPosition: result.waitlistPosition ?? void 0,
1020
+ usedMembership: result.paymentMethod === "membership"
1021
+ });
1022
+ setStep(result.status === "waitlist" ? "waitlist" : "success");
1023
+ } catch (error) {
1024
+ const message = error instanceof Error ? error.message : "Booking failed";
1025
+ setErrorMessage(message);
1026
+ setStep("error");
1027
+ }
1028
+ };
1029
+ const handlePaymentContinue = async () => {
1030
+ if (!selectedCourse) return;
1031
+ if (!handleValidateForm()) return;
1032
+ if (missingPricingSelectionMessage) {
1033
+ setErrorMessage(missingPricingSelectionMessage);
1034
+ setStep("error");
1035
+ return;
1036
+ }
1037
+ const requiresPayment = selectedCoursePricing?.selectedRequiresPayment ?? false;
1038
+ if (!waitlistClaim.claim && courseEligibility?.isFull && !courseEligibility.canJoinWaitlist && showSoldOutCourseError()) {
1039
+ return;
1040
+ }
1041
+ if (!requiresPayment) {
1042
+ setStep("enter-details");
1043
+ return;
1044
+ }
1045
+ if (showSpamProtection && !altchaSolution) {
1046
+ setErrorMessage("Please complete the verification check.");
1047
+ setStep("error");
1048
+ return;
1049
+ }
1050
+ if (paymentChoice === "membership") {
1051
+ if (!selectedMembershipId) {
1052
+ setErrorMessage("Select a membership to enroll.");
1053
+ setStep("error");
1054
+ return;
1055
+ }
1056
+ setStep("submitting");
1057
+ try {
1058
+ const result = await api.enrollInCourse({
1059
+ courseId: selectedCourse.id,
1060
+ ...selectedCoursePricingPayload,
1061
+ email: formData.email.trim().toLowerCase(),
1062
+ name: formData.name.trim(),
1063
+ phone: formData.phone.trim() || void 0,
1064
+ formData: { notes: formData.notes.trim() || void 0 },
1065
+ altchaSolution,
1066
+ paymentMethod: "membership",
1067
+ customerMembershipId: selectedMembershipId,
1068
+ claimToken: waitlistClaim.token ?? void 0
1069
+ });
1070
+ setEnrollmentResult({
1071
+ status: result.status,
1072
+ waitlistPosition: result.waitlistPosition ?? void 0,
1073
+ usedMembership: true
1074
+ });
1075
+ setStep(result.status === "waitlist" ? "waitlist" : "success");
1076
+ } catch (error) {
1077
+ const message = error instanceof Error ? error.message : "Booking failed";
1078
+ setErrorMessage(message);
1079
+ setStep("error");
1080
+ }
1081
+ return;
1082
+ }
1083
+ setStep("submitting");
1084
+ try {
1085
+ const { successUrl, cancelUrl } = buildCheckoutReturnUrls("enrollment", COURSE_REGISTRATION_ANCHOR_ID);
1086
+ const checkoutResult = await api.createCourseCheckout({
1087
+ courseId: selectedCourse.id,
1088
+ ...selectedCoursePricingPayload,
1089
+ email: formData.email.trim().toLowerCase(),
1090
+ name: formData.name.trim(),
1091
+ phone: formData.phone.trim() || void 0,
1092
+ formData: { notes: formData.notes.trim() || void 0 },
1093
+ altchaSolution,
1094
+ discountCode: discountCode.trim() ? discountCode.trim() : void 0,
1095
+ successUrl,
1096
+ cancelUrl,
1097
+ claimToken: waitlistClaim.token ?? void 0
1098
+ });
1099
+ if (checkoutResult.bypassed) {
1100
+ setEnrollmentResult({
1101
+ status: "enrolled",
1102
+ waitlistPosition: void 0,
1103
+ usedMembership: false
1104
+ });
1105
+ setStep("success");
1106
+ return;
1107
+ }
1108
+ if (!checkoutResult.checkoutUrl) {
1109
+ throw new Error("Checkout URL missing");
1110
+ }
1111
+ setStep("redirecting");
1112
+ window.location.href = checkoutResult.checkoutUrl;
1113
+ } catch (error) {
1114
+ const message = error instanceof Error ? error.message : "Checkout failed";
1115
+ setErrorMessage(message);
1116
+ setStep("error");
1117
+ }
1118
+ };
1119
+ const handleRetry = () => {
1120
+ setStep(scopedCourses.length === 1 ? "enter-details" : "select-course");
1121
+ setErrorMessage("");
1122
+ };
1123
+ const handleBookingModeChange = (mode) => {
1124
+ setBookingMode(mode);
1125
+ if (mode === "membership") {
1126
+ setPaymentChoice("membership");
1127
+ } else {
1128
+ setPaymentChoice("checkout");
1129
+ }
1130
+ };
1131
+ const courseDiscountContext = useMemo(() => {
1132
+ if (!selectedCourse) return null;
1133
+ if (!selectedCoursePricing?.selectedRequiresPayment) return null;
1134
+ if (paymentChoice !== "checkout") return null;
1135
+ return { type: "course", courseId: selectedCourse.id };
1136
+ }, [selectedCourse, selectedCoursePricing?.selectedRequiresPayment, paymentChoice]);
1137
+ useEffect(() => {
1138
+ let cancelled = false;
1139
+ async function checkDiscountAvailability() {
1140
+ if (!courseDiscountContext) {
1141
+ setDiscountsAvailable(false);
1142
+ setManualCodeEntryAllowed(false);
1143
+ setDiscountsAvailableLoading(false);
1144
+ setDiscountUiOpen(false);
1145
+ setDiscountCode("");
1146
+ return;
1147
+ }
1148
+ setDiscountsAvailableLoading(true);
1149
+ try {
1150
+ const res = await api.getDiscountsAvailable({
1151
+ context: courseDiscountContext,
1152
+ subtotalCents: selectedCoursePricing?.selectedPriceAmount ?? void 0
1153
+ });
1154
+ if (cancelled) return;
1155
+ setDiscountsAvailable(res.hasDiscounts);
1156
+ setManualCodeEntryAllowed(res.manualCodeEntryAllowed);
1157
+ if (!res.hasDiscounts || !res.manualCodeEntryAllowed) {
1158
+ setDiscountUiOpen(false);
1159
+ setDiscountCode("");
1160
+ }
1161
+ } catch {
1162
+ if (cancelled) return;
1163
+ setDiscountsAvailable(false);
1164
+ setManualCodeEntryAllowed(false);
1165
+ setDiscountUiOpen(false);
1166
+ setDiscountCode("");
1167
+ } finally {
1168
+ if (!cancelled) setDiscountsAvailableLoading(false);
1169
+ }
1170
+ }
1171
+ checkDiscountAvailability();
1172
+ return () => {
1173
+ cancelled = true;
1174
+ };
1175
+ }, [api, courseDiscountContext, selectedCoursePricing?.selectedPriceAmount]);
1176
+ const hasCoursePayment = resolveHasCoursePayment(selectedCoursePricing);
1177
+ const canUseMembership = hasCoursePayment && selectedCourse?.acceptsMemberships === true && portalAuthState === "authenticated" && eligibleMemberships.length > 0;
1178
+ const hasPreSelectedCourse = scopedCourses.length === 1;
1179
+ const isMembershipFastTrack = canUseMembership && eligibleMemberships.length === 1 && bookingMode === "membership";
1180
+ const wizardSteps = useMemo(
1181
+ () => getWizardStepsForContext(
1182
+ hasPreSelectedCourse,
1183
+ hasCoursePayment,
1184
+ canUseMembership,
1185
+ bookingMode,
1186
+ isMembershipFastTrack
1187
+ ),
1188
+ [hasPreSelectedCourse, hasCoursePayment, canUseMembership, bookingMode, isMembershipFastTrack]
1189
+ );
1190
+ const currentStepNumber = getStepNumber(step, wizardSteps);
1191
+ const totalSteps = wizardSteps.length;
1192
+ const stepLabels = getStepLabels(wizardSteps);
1193
+ const canShowPortalAuthSidebarOnCurrentStep = step !== "success" && step !== "waitlist" && step !== "error";
1194
+ const showPortalAuthSidebar = portalAuthState === "unauthenticated" && waitlistClaim.requiresPortalLogin && canShowPortalAuthSidebarOnCurrentStep || portalAuthState === "unauthenticated" && hasCoursePayment && selectedCourse?.acceptsMemberships === true && step !== "select-course" && canShowPortalAuthSidebarOnCurrentStep;
1195
+ const handleLogout = portalAuthState === "authenticated" ? () => logoutPortal(COURSE_REGISTRATION_ANCHOR_ID) : void 0;
1196
+ return {
1197
+ api,
1198
+ scopedCourses,
1199
+ step,
1200
+ setStep,
1201
+ selectedCourse,
1202
+ selectedPricingOptionId,
1203
+ setSelectedPricingOptionId,
1204
+ selectedCoursePricing,
1205
+ setSelectedCourse,
1206
+ formData,
1207
+ setFormData,
1208
+ errors,
1209
+ enrollmentResult,
1210
+ errorMessage,
1211
+ portalAuthState,
1212
+ handleLogout,
1213
+ altchaSolution,
1214
+ setAltchaSolution,
1215
+ showSpamProtection,
1216
+ eligibilityLoading,
1217
+ eligibilityError,
1218
+ courseEligibility,
1219
+ eligibleMemberships,
1220
+ paymentChoice,
1221
+ setPaymentChoice,
1222
+ selectedMembershipId,
1223
+ setSelectedMembershipId,
1224
+ discountUiOpen,
1225
+ setDiscountUiOpen,
1226
+ discountCode,
1227
+ setDiscountCode,
1228
+ discountsAvailable,
1229
+ manualCodeEntryAllowed,
1230
+ discountsAvailableLoading,
1231
+ canUseMembership,
1232
+ waitlistClaim,
1233
+ // New booking mode state
1234
+ bookingMode,
1235
+ // Wizard step info
1236
+ wizardSteps,
1237
+ currentStepNumber,
1238
+ totalSteps,
1239
+ stepLabels,
1240
+ showPortalAuthSidebar,
1241
+ hasPreSelectedCourse,
1242
+ hasCoursePayment,
1243
+ // Handlers
1244
+ handleCourseSelect,
1245
+ handleSubmit,
1246
+ handlePaymentContinue,
1247
+ handleRetry,
1248
+ handleBookingModeChange
1249
+ };
1250
+ }
1251
+ function CourseRegistrationWizard(props) {
1252
+ const {
1253
+ showSessions = true,
1254
+ showPrice = true,
1255
+ showCapacity = true,
1256
+ showVenue = true,
1257
+ successMessage = "You're booked in! Check your email for confirmation and session details.",
1258
+ waitlistMessage = "You've been added to the waitlist. We'll email you a claim link if a spot opens up.",
1259
+ membershipMessage = "Booked using your membership! Check your email for session details.",
1260
+ buttonText = "Book course",
1261
+ buttonVariant = "primary",
1262
+ className
1263
+ } = props;
1264
+ const {
1265
+ api,
1266
+ scopedCourses,
1267
+ step,
1268
+ setStep,
1269
+ selectedCourse,
1270
+ selectedPricingOptionId,
1271
+ setSelectedPricingOptionId,
1272
+ selectedCoursePricing,
1273
+ formData,
1274
+ setFormData,
1275
+ errors,
1276
+ enrollmentResult,
1277
+ errorMessage,
1278
+ portalAuthState,
1279
+ handleLogout,
1280
+ setAltchaSolution,
1281
+ showSpamProtection,
1282
+ eligibilityLoading,
1283
+ eligibilityError,
1284
+ courseEligibility,
1285
+ eligibleMemberships,
1286
+ paymentChoice,
1287
+ setPaymentChoice,
1288
+ selectedMembershipId,
1289
+ setSelectedMembershipId,
1290
+ discountUiOpen,
1291
+ setDiscountUiOpen,
1292
+ discountCode,
1293
+ setDiscountCode,
1294
+ discountsAvailable,
1295
+ manualCodeEntryAllowed,
1296
+ discountsAvailableLoading,
1297
+ waitlistClaim,
1298
+ // New booking mode state
1299
+ bookingMode,
1300
+ // Wizard step info
1301
+ currentStepNumber,
1302
+ totalSteps,
1303
+ stepLabels,
1304
+ showPortalAuthSidebar,
1305
+ // Handlers
1306
+ handleCourseSelect,
1307
+ handleSubmit,
1308
+ handlePaymentContinue,
1309
+ handleRetry,
1310
+ handleBookingModeChange
1311
+ } = useCourseRegistrationWizard(props);
1312
+ const buttonClass = themeButtonClassName({ variant: buttonVariant, size: "md" });
1313
+ if (scopedCourses.length === 0) {
1314
+ return /* @__PURE__ */ jsx(
1315
+ "div",
1316
+ {
1317
+ id: COURSE_REGISTRATION_ANCHOR_ID,
1318
+ className: `course-registration ${className || ""}`,
1319
+ "data-block": "course-registration",
1320
+ children: /* @__PURE__ */ jsx("div", { className: "cr-empty", children: /* @__PURE__ */ jsx("p", { className: "cr-empty__text", children: "No courses available to book." }) })
1321
+ }
1322
+ );
1323
+ }
1324
+ const allowedStatuses = api.config.isPreview ? ["active", "draft"] : ["active"];
1325
+ const hasEnrollableCourses = scopedCourses.some(
1326
+ (c) => !c.status || allowedStatuses.includes(c.status)
1327
+ );
1328
+ if (!hasEnrollableCourses) {
1329
+ return /* @__PURE__ */ jsx(
1330
+ "div",
1331
+ {
1332
+ id: COURSE_REGISTRATION_ANCHOR_ID,
1333
+ className: `course-registration ${className || ""}`,
1334
+ "data-block": "course-registration",
1335
+ children: /* @__PURE__ */ jsx("div", { className: "cr-empty", children: /* @__PURE__ */ jsx("p", { className: "cr-empty__text", children: "Booking is not currently available for this course." }) })
1336
+ }
1337
+ );
1338
+ }
1339
+ const canShowDiscountUi = !discountsAvailableLoading && paymentChoice === "checkout" && discountsAvailable && manualCodeEntryAllowed;
1340
+ const courseSoldOut = waitlistClaim.claim === null && courseEligibility?.isFull === true && courseEligibility.canJoinWaitlist !== true;
1341
+ const detailsSubmitLabel = courseEligibility?.canJoinWaitlist ? "Join waitlist" : buttonText;
1342
+ const pricingSelectionMissing = selectedCoursePricing?.requiresSelection === true && !selectedCoursePricing.selectedOption;
1343
+ const pricingUnavailable = selectedCoursePricing?.display.kind === "unavailable";
1344
+ const selectedCourseWithPricing = selectedCourse && selectedCoursePricing ? {
1345
+ course: selectedCourse,
1346
+ pricing: selectedCoursePricing
1347
+ } : null;
1348
+ const showStepIndicator = WIZARD_STEPS.includes(step) && totalSteps > 1;
1349
+ const showSidebarLayout = showPortalAuthSidebar;
1350
+ return /* @__PURE__ */ jsx(
1351
+ "div",
1352
+ {
1353
+ id: COURSE_REGISTRATION_ANCHOR_ID,
1354
+ className: `course-registration ${className || ""}`,
1355
+ "data-block": "course-registration",
1356
+ children: /* @__PURE__ */ jsx("div", { className: "cr-container", children: /* @__PURE__ */ jsxs(
1357
+ "div",
1358
+ {
1359
+ className: `cr-layout${showSidebarLayout ? " cr-layout--with-sidebar" : ""}`,
1360
+ children: [
1361
+ /* @__PURE__ */ jsxs("div", { className: "cr-layout__main", children: [
1362
+ showStepIndicator && /* @__PURE__ */ jsx(
1363
+ StepIndicator2,
1364
+ {
1365
+ currentStep: currentStepNumber,
1366
+ totalSteps,
1367
+ stepLabels
1368
+ }
1369
+ ),
1370
+ step === "select-course" && /* @__PURE__ */ jsx("div", { className: "cr-course-list", children: scopedCourses.map((course) => /* @__PURE__ */ jsx(
1371
+ CourseCard,
1372
+ {
1373
+ course,
1374
+ isSelected: selectedCourse?.id === course.id,
1375
+ onSelect: () => handleCourseSelect(course),
1376
+ showSessions,
1377
+ showPrice,
1378
+ showCapacity,
1379
+ showVenue
1380
+ },
1381
+ course.id
1382
+ )) }),
1383
+ step === "enter-details" && selectedCourseWithPricing && /* @__PURE__ */ jsxs("div", { className: "cr-details", children: [
1384
+ /* @__PURE__ */ jsx(
1385
+ CourseSummary,
1386
+ {
1387
+ course: selectedCourseWithPricing.course,
1388
+ pricing: selectedCourseWithPricing.pricing,
1389
+ showSessions,
1390
+ showVenue,
1391
+ showPrice,
1392
+ showMembershipNote: selectedCourseWithPricing.course.acceptsMemberships && portalAuthState === "authenticated"
1393
+ }
1394
+ ),
1395
+ selectedCourseWithPricing.course.enrollmentOpen === false ? /* @__PURE__ */ jsxs("div", { className: "cr-form", children: [
1396
+ scopedCourses.length > 1 && /* @__PURE__ */ jsx("div", { className: "cr-actions", children: /* @__PURE__ */ jsx(
1397
+ "button",
1398
+ {
1399
+ type: "button",
1400
+ onClick: () => setStep("select-course"),
1401
+ className: "cr-back-btn",
1402
+ children: "\u2190 Back"
1403
+ }
1404
+ ) }),
1405
+ /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "Registration closed." })
1406
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1407
+ /* @__PURE__ */ jsxs(
1408
+ "form",
1409
+ {
1410
+ className: "cr-form",
1411
+ onSubmit: (e) => {
1412
+ e.preventDefault();
1413
+ handleSubmit();
1414
+ },
1415
+ children: [
1416
+ /* @__PURE__ */ jsx(
1417
+ EnrollmentForm,
1418
+ {
1419
+ formData,
1420
+ errors,
1421
+ onChange: setFormData,
1422
+ isEmailLocked: portalAuthState === "authenticated" || waitlistClaim.claim !== null,
1423
+ isLoading: portalAuthState === "checking" || waitlistClaim.isLoading,
1424
+ onLogout: handleLogout
1425
+ }
1426
+ ),
1427
+ /* @__PURE__ */ jsx(
1428
+ CoursePricingSelector,
1429
+ {
1430
+ pricing: selectedCourseWithPricing.pricing,
1431
+ selectedPricingOptionId,
1432
+ onSelect: setSelectedPricingOptionId
1433
+ }
1434
+ ),
1435
+ showSpamProtection && /* @__PURE__ */ jsx("div", { className: "cr-altcha", children: /* @__PURE__ */ jsx(
1436
+ AltchaWidget,
1437
+ {
1438
+ challengeUrl: getChallengeUrl(),
1439
+ onSolutionChange: setAltchaSolution
1440
+ }
1441
+ ) }),
1442
+ canShowDiscountUi && /* @__PURE__ */ jsx(
1443
+ DiscountCodeSection,
1444
+ {
1445
+ isOpen: discountUiOpen,
1446
+ code: discountCode,
1447
+ onOpen: () => setDiscountUiOpen(true),
1448
+ onChange: setDiscountCode,
1449
+ onRemove: () => {
1450
+ setDiscountCode("");
1451
+ setDiscountUiOpen(false);
1452
+ }
1453
+ }
1454
+ ),
1455
+ /* @__PURE__ */ jsxs("div", { className: "cr-actions", children: [
1456
+ scopedCourses.length > 1 && /* @__PURE__ */ jsx(
1457
+ "button",
1458
+ {
1459
+ type: "button",
1460
+ onClick: () => setStep("select-course"),
1461
+ className: "cr-back-btn",
1462
+ children: "\u2190 Back"
1463
+ }
1464
+ ),
1465
+ /* @__PURE__ */ jsx(
1466
+ "button",
1467
+ {
1468
+ type: "submit",
1469
+ className: buttonClass,
1470
+ disabled: courseSoldOut || eligibilityLoading || pricingSelectionMissing || pricingUnavailable,
1471
+ children: eligibilityLoading ? "Checking availability\u2026" : courseSoldOut ? "Sold out" : detailsSubmitLabel
1472
+ }
1473
+ )
1474
+ ] })
1475
+ ]
1476
+ }
1477
+ ),
1478
+ courseSoldOut && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "This course is fully booked and there is no waitlist available right now." }),
1479
+ pricingSelectionMissing && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "Choose a pricing option to continue." }),
1480
+ pricingUnavailable && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--muted", children: "Pricing is not available for this course right now." }),
1481
+ eligibilityError && /* @__PURE__ */ jsx("p", { className: "cr-text cr-text--error", children: eligibilityError })
1482
+ ] })
1483
+ ] }),
1484
+ step === "booking-mode" && selectedCourseWithPricing && /* @__PURE__ */ jsxs("div", { className: "cr-details", children: [
1485
+ /* @__PURE__ */ jsx(
1486
+ CourseSummary,
1487
+ {
1488
+ course: selectedCourseWithPricing.course,
1489
+ pricing: selectedCourseWithPricing.pricing,
1490
+ showSessions,
1491
+ showVenue,
1492
+ showPrice,
1493
+ showMembershipNote: false
1494
+ }
1495
+ ),
1496
+ /* @__PURE__ */ jsx(
1497
+ BookingModeStep,
1498
+ {
1499
+ bookingMode,
1500
+ onBookingModeChange: handleBookingModeChange,
1501
+ eligibleMemberships,
1502
+ selectedMembershipId,
1503
+ onMembershipSelect: setSelectedMembershipId,
1504
+ priceCents: selectedCoursePricing?.selectedPriceAmount ?? null,
1505
+ currency: selectedCourseWithPricing.course.currency ?? "GBP"
1506
+ }
1507
+ ),
1508
+ /* @__PURE__ */ jsxs("div", { className: "cr-actions", children: [
1509
+ /* @__PURE__ */ jsx(
1510
+ "button",
1511
+ {
1512
+ type: "button",
1513
+ onClick: () => setStep("enter-details"),
1514
+ className: "cr-back-btn",
1515
+ children: "\u2190 Back"
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ jsx(
1519
+ "button",
1520
+ {
1521
+ type: "button",
1522
+ className: buttonClass,
1523
+ onClick: handlePaymentContinue,
1524
+ disabled: !bookingMode || bookingMode === "membership" && !selectedMembershipId,
1525
+ children: bookingMode === "membership" ? "Book course" : "Continue to payment"
1526
+ }
1527
+ )
1528
+ ] })
1529
+ ] }),
1530
+ step === "select-payment" && selectedCourseWithPricing && /* @__PURE__ */ jsxs("div", { className: "cr-details", children: [
1531
+ /* @__PURE__ */ jsx(
1532
+ CourseSummary,
1533
+ {
1534
+ course: selectedCourseWithPricing.course,
1535
+ pricing: selectedCourseWithPricing.pricing,
1536
+ showSessions,
1537
+ showVenue,
1538
+ showPrice,
1539
+ showMembershipNote: false
1540
+ }
1541
+ ),
1542
+ /* @__PURE__ */ jsxs("div", { className: "cr-form", children: [
1543
+ /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
1544
+ /* @__PURE__ */ jsx("label", { className: "cr-form__label", children: "Choose how to book" }),
1545
+ /* @__PURE__ */ jsxs("div", { className: "cr-payment__options", children: [
1546
+ /* @__PURE__ */ jsxs("label", { className: "cr-payment__option", children: [
1547
+ /* @__PURE__ */ jsx(
1548
+ "input",
1549
+ {
1550
+ type: "radio",
1551
+ name: "paymentChoice",
1552
+ checked: paymentChoice === "membership",
1553
+ onChange: () => setPaymentChoice("membership")
1554
+ }
1555
+ ),
1556
+ /* @__PURE__ */ jsx("span", { children: "Use a membership (free)" })
1557
+ ] }),
1558
+ paymentChoice === "membership" && eligibleMemberships.length > 1 && /* @__PURE__ */ jsxs("div", { className: "cr-form__field", children: [
1559
+ /* @__PURE__ */ jsx(
1560
+ "label",
1561
+ {
1562
+ htmlFor: "cr-membership",
1563
+ className: "cr-form__label",
1564
+ children: "Membership"
1565
+ }
1566
+ ),
1567
+ /* @__PURE__ */ jsxs(
1568
+ "select",
1569
+ {
1570
+ id: "cr-membership",
1571
+ className: "cr-form__input",
1572
+ value: selectedMembershipId ?? "",
1573
+ onChange: (e) => setSelectedMembershipId(e.target.value || null),
1574
+ children: [
1575
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a membership\u2026" }),
1576
+ eligibleMemberships.map((m) => /* @__PURE__ */ jsx(
1577
+ "option",
1578
+ {
1579
+ value: m.customerMembershipId,
1580
+ children: m.membershipName
1581
+ },
1582
+ m.customerMembershipId
1583
+ ))
1584
+ ]
1585
+ }
1586
+ )
1587
+ ] }),
1588
+ /* @__PURE__ */ jsxs("label", { className: "cr-payment__option", children: [
1589
+ /* @__PURE__ */ jsx(
1590
+ "input",
1591
+ {
1592
+ type: "radio",
1593
+ name: "paymentChoice",
1594
+ checked: paymentChoice === "checkout",
1595
+ onChange: () => setPaymentChoice("checkout")
1596
+ }
1597
+ ),
1598
+ /* @__PURE__ */ jsx("span", { children: "Pay by card" })
1599
+ ] })
1600
+ ] })
1601
+ ] }),
1602
+ canShowDiscountUi && /* @__PURE__ */ jsx(
1603
+ DiscountCodeSection,
1604
+ {
1605
+ isOpen: discountUiOpen,
1606
+ code: discountCode,
1607
+ onOpen: () => setDiscountUiOpen(true),
1608
+ onChange: setDiscountCode,
1609
+ onRemove: () => {
1610
+ setDiscountCode("");
1611
+ setDiscountUiOpen(false);
1612
+ }
1613
+ }
1614
+ ),
1615
+ /* @__PURE__ */ jsxs("div", { className: "cr-actions", children: [
1616
+ /* @__PURE__ */ jsx(
1617
+ "button",
1618
+ {
1619
+ type: "button",
1620
+ onClick: () => setStep("enter-details"),
1621
+ className: "cr-back-btn",
1622
+ children: "\u2190 Back"
1623
+ }
1624
+ ),
1625
+ /* @__PURE__ */ jsx(
1626
+ "button",
1627
+ {
1628
+ type: "button",
1629
+ className: buttonClass,
1630
+ onClick: handlePaymentContinue,
1631
+ children: "Continue"
1632
+ }
1633
+ )
1634
+ ] })
1635
+ ] })
1636
+ ] }),
1637
+ step === "submitting" && /* @__PURE__ */ jsxs("div", { className: "cr-center", children: [
1638
+ /* @__PURE__ */ jsx(SpinnerNode, { size: "md", srLabel: "Processing" }),
1639
+ /* @__PURE__ */ jsx("p", { className: "cr-text--muted", children: "Booking course..." })
1640
+ ] }),
1641
+ step === "redirecting" && /* @__PURE__ */ jsxs("div", { className: "cr-center", children: [
1642
+ /* @__PURE__ */ jsx(SpinnerNode, { size: "md", srLabel: "Redirecting" }),
1643
+ /* @__PURE__ */ jsx("p", { className: "cr-text--muted", children: "Redirecting to payment..." })
1644
+ ] }),
1645
+ step === "verifying-payment" && /* @__PURE__ */ jsxs("div", { className: "cr-center", children: [
1646
+ /* @__PURE__ */ jsx(SpinnerNode, { size: "md", srLabel: "Verifying payment" }),
1647
+ /* @__PURE__ */ jsx("p", { className: "cr-text--muted", children: "Verifying payment..." })
1648
+ ] }),
1649
+ step === "verifying-timeout" && /* @__PURE__ */ jsx(
1650
+ VerifyingTimeoutStep,
1651
+ {
1652
+ buttonVariant,
1653
+ onCheckAgain: handleRetry
1654
+ }
1655
+ ),
1656
+ step === "payment-failed" && /* @__PURE__ */ jsx(
1657
+ PaymentFailedStep,
1658
+ {
1659
+ message: errorMessage,
1660
+ buttonVariant,
1661
+ onRetry: handleRetry
1662
+ }
1663
+ ),
1664
+ step === "success" && /* @__PURE__ */ jsx(
1665
+ SuccessStep,
1666
+ {
1667
+ usedMembership: enrollmentResult?.usedMembership,
1668
+ successMessage,
1669
+ membershipMessage
1670
+ }
1671
+ ),
1672
+ step === "waitlist" && /* @__PURE__ */ jsx(
1673
+ WaitlistStep,
1674
+ {
1675
+ message: waitlistMessage,
1676
+ position: enrollmentResult?.waitlistPosition
1677
+ }
1678
+ ),
1679
+ step === "error" && /* @__PURE__ */ jsx(
1680
+ ErrorStep,
1681
+ {
1682
+ message: errorMessage,
1683
+ buttonVariant,
1684
+ onRetry: handleRetry
1685
+ }
1686
+ )
1687
+ ] }),
1688
+ showSidebarLayout && /* @__PURE__ */ jsx("div", { className: "cr-layout__sidebar", children: /* @__PURE__ */ jsx(
1689
+ PortalAuthSidebar,
1690
+ {
1691
+ autoOpen: waitlistClaim.requiresPortalLogin,
1692
+ loginMessage: waitlistClaim.loginMessage,
1693
+ variant: waitlistClaim.requiresPortalLogin ? "waitlist-claim" : "membership"
1694
+ }
1695
+ ) })
1696
+ ]
1697
+ }
1698
+ ) })
1699
+ }
1700
+ );
1701
+ }
1702
+ function toCourseRegistrationClientProps(props) {
1703
+ return {
1704
+ ...props.render.hydration,
1705
+ buttonText: props.render.display.summaryButtonText,
1706
+ className: props.render.display.className ?? void 0
1707
+ };
1708
+ }
1709
+ function CourseRegistrationClient(props) {
1710
+ return /* @__PURE__ */ jsx(CourseRegistrationErrorBoundary, { className: props.className, children: /* @__PURE__ */ jsx(CourseRegistrationWizard, { ...props }) });
1711
+ }
1712
+ function CourseRegistrationIslandClient(props) {
1713
+ return /* @__PURE__ */ jsx(CourseRegistrationClient, { ...toCourseRegistrationClientProps(props) });
1714
+ }
1715
+ var decode = defineIslandPropsDecoder("course-registration");
1716
+ var enhance = (root, props) => {
1717
+ const { siteId, apiBaseUrl, stage, portalToken } = props;
1718
+ if (!siteId) {
1719
+ throw new Error("[sdk:islands] course-registration requires siteId");
1720
+ }
1721
+ if (!apiBaseUrl) {
1722
+ throw new Error("[sdk:islands] course-registration requires apiBaseUrl");
1723
+ }
1724
+ const apiConfig = {
1725
+ baseUrl: apiBaseUrl,
1726
+ auth: portalToken ? { type: "bearer", token: portalToken } : { type: "none" },
1727
+ siteId,
1728
+ isPreview: stage === "preview"
1729
+ };
1730
+ return mountReactIsland(root, ({ root: reactRoot }) => {
1731
+ reactRoot.render(
1732
+ /* @__PURE__ */ jsx(BlockApiProvider, { config: apiConfig, children: /* @__PURE__ */ jsx(CourseRegistrationIslandClient, { ...props }) })
1733
+ );
1734
+ });
1735
+ };
1736
+
1737
+ export { decode, enhance };