@riverbankcms/sdk 0.149.0 → 0.150.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 (165) hide show
  1. package/dist/EventEmbeddedCheckoutStep-D00Kc8Zj.mjs +285 -0
  2. package/dist/{PreviewEditorSidebar-DBKln68M.mjs → PreviewEditorSidebar-7ZhmmnBB.mjs} +3 -3
  3. package/dist/{PreviewEditorUI-DpRX4Mum.mjs → PreviewEditorUI-BIJucTRd.mjs} +2 -2
  4. package/dist/{SdkPreviewModeRuntime-DJPmPY-r.mjs → SdkPreviewModeRuntime-tj45gmCX.mjs} +4 -3
  5. package/dist/{SiteChromeCustomizeContext-JYA0cJ_Z.mjs → SiteChromeCustomizeContext-BFOt0ss-.mjs} +4454 -4059
  6. package/dist/_dts/ai/src/contracts/proposals.d.ts +660 -0
  7. package/dist/_dts/ai/src/contracts/siteStyleProposalSchemas.d.ts +510 -0
  8. package/dist/_dts/api/src/billing.d.ts +2 -2
  9. package/dist/_dts/api/src/contentRuntime.d.ts +5 -1
  10. package/dist/_dts/api/src/footerCompositionContracts.d.ts +9 -0
  11. package/dist/_dts/api/src/footerSettings.d.ts +3 -3
  12. package/dist/_dts/api/src/index.d.ts +1 -1
  13. package/dist/_dts/api/src/navigation/bookingPortalLink.d.ts +13 -0
  14. package/dist/_dts/api/src/navigation.d.ts +1 -0
  15. package/dist/_dts/api/src/publicExperience/eventEndpoints.d.ts +9 -173
  16. package/dist/_dts/api/src/sdkThemeWire.d.ts +12 -0
  17. package/dist/_dts/api/src/siteManagementEndpoints.d.ts +3 -3
  18. package/dist/_dts/api/src/siteSettings.d.ts +2 -0
  19. package/dist/_dts/blocks/src/client/site-chrome.d.ts +1 -1
  20. package/dist/_dts/blocks/src/client.d.ts +2 -1
  21. package/dist/_dts/blocks/src/rendering/server.d.ts +1 -1
  22. package/dist/_dts/blocks/src/system/blocks/site-footer-runtime.d.ts +30 -3
  23. package/dist/_dts/blocks/src/system/designCapabilities.d.ts +10 -1
  24. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +1 -1
  25. package/dist/_dts/blocks/src/system/runtime/api/types/events.d.ts +38 -18
  26. package/dist/_dts/blocks/src/system/runtime/hooks/useHeadlessEventRegistrationFlow.d.ts +1 -1
  27. package/dist/_dts/blocks/src/system/runtime/nodes/booking-form.server.d.ts +6 -0
  28. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventEmbeddedCheckoutStep.d.ts +13 -0
  29. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationPayment.d.ts +15 -0
  30. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/EventRegistrationWizardActions.d.ts +0 -1
  31. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/FullyBookedStep.d.ts +6 -0
  32. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentBlockingPanel.d.ts +3 -0
  33. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/PaymentSelectionStep.d.ts +2 -0
  34. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +15 -5
  35. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.d.ts +4 -2
  36. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/paymentLocation.d.ts +3 -0
  37. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/paymentResponse.d.ts +72 -0
  38. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationCompletion.d.ts +66 -5
  39. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.d.ts +1 -4
  40. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.d.ts +13 -5
  41. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/sessionStorage.d.ts +11 -52
  42. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +19 -2
  43. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +7 -42
  44. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +7 -8
  45. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +90 -24
  46. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useGuestRegistrationRecovery.d.ts +30 -0
  47. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.d.ts +24 -12
  48. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useRegistrationAdmission.d.ts +18 -0
  49. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/wizardSteps.d.ts +6 -1
  50. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration.interactive.d.ts +1 -1
  51. package/dist/_dts/blocks/src/system/runtime/shared/stripe/browserStripe.d.ts +8 -0
  52. package/dist/_dts/blocks/src/system/transforms/registry/eventCalendar.d.ts +8 -0
  53. package/dist/_dts/commerce/src/purchase/checkoutCopy.d.ts +10 -0
  54. package/dist/_dts/commerce/src/purchase/checkoutPresentation.d.ts +49 -0
  55. package/dist/_dts/commerce/src/purchase/checkoutSessionMetadata.d.ts +4 -4
  56. package/dist/_dts/commerce/src/purchase/checkoutStripeFinalizationPlanning.d.ts +5 -1
  57. package/dist/_dts/commerce/src/purchase/event-registration-errors.d.ts +9 -0
  58. package/dist/_dts/commerce/src/purchase/index.d.ts +2 -0
  59. package/dist/_dts/commerce/src/purchase/orderItemMetadata.d.ts +1 -0
  60. package/dist/_dts/commerce/src/purchase/stripeCheckoutReturnUrl.d.ts +9 -2
  61. package/dist/_dts/commerce/src/purchase/types.d.ts +5 -6
  62. package/dist/_dts/content-editor/src/FooterComposerOverview.d.ts +3 -2
  63. package/dist/_dts/content-editor/src/footerPreviewRuntime.d.ts +6 -1
  64. package/dist/_dts/content-editor/src/useFooterComposerState.d.ts +7 -0
  65. package/dist/_dts/core/src/index.d.ts +1 -0
  66. package/dist/_dts/core/src/patch/index.d.ts +30 -0
  67. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +30 -1
  68. package/dist/_dts/sdk/src/bookings/index.d.ts +2 -1
  69. package/dist/_dts/sdk/src/bookings/types.d.ts +1 -1
  70. package/dist/_dts/sdk/src/version.d.ts +1 -1
  71. package/dist/_dts/theme-core/src/blocks/index.d.ts +1 -0
  72. package/dist/_dts/theme-core/src/blocks/opaquePanels.d.ts +147 -0
  73. package/dist/_dts/theme-core/src/boxBackgroundSchema.d.ts +22 -0
  74. package/dist/_dts/theme-core/src/buttons/buttonSurfaceContext.d.ts +22 -0
  75. package/dist/_dts/theme-core/src/cards/roles.d.ts +12 -0
  76. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  77. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +198 -0
  78. package/dist/_dts/theme-core/src/palette/utils/index.d.ts +2 -0
  79. package/dist/_dts/theme-core/src/palette/utils/scrimContrast.d.ts +22 -0
  80. package/dist/_dts/theme-core/src/runtime/bridgeColourTokens.d.ts +10 -0
  81. package/dist/_dts/theme-core/src/schema.d.ts +330 -0
  82. package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +0 -51
  83. package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +0 -51
  84. package/dist/_dts/theme-core/src/site-styles/lookResolution.d.ts +11 -0
  85. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorCapabilities.d.ts +7 -1
  86. package/dist/_dts/theme-core/src/site-styles/pageDesignEditorModel.d.ts +7 -1
  87. package/dist/_dts/theme-core/src/site-styles/previewRuntimeCapabilities.d.ts +12 -6
  88. package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +7 -4
  89. package/dist/_dts/theme-core/src/site-styles/sectionSurfaceBackgrounds.d.ts +65 -0
  90. package/dist/_dts/theme-core/src/site-styles/types.d.ts +17 -0
  91. package/dist/_dts/theme-core/src/surfaces/opaqueSurface.d.ts +43 -0
  92. package/dist/client/{BookingMailingListNote-Cr3EeTP5.mjs → BookingMailingListNote-CYHOufsI.mjs} +10 -440
  93. package/dist/client/{CheckboxChoice-B33MqoUo.mjs → CheckboxChoice-M-3HycLw.mjs} +1 -1
  94. package/dist/client/{DemoBookingPortalAccess-BFGB1Fay.mjs → DemoBookingPortalAccess-Cvr9O_ca.mjs} +2 -2
  95. package/dist/client/{EventCombined.client-DKYV0_ft.mjs → EventCombined.client-m5UZIobP.mjs} +7 -7
  96. package/dist/client/EventEmbeddedCheckoutStep-DL6zFAkA.mjs +289 -0
  97. package/dist/client/{EventListing.client-B9UIcEYc.mjs → EventListing.client-DeMfCM04.mjs} +2 -2
  98. package/dist/client/{LeafletStaticMap-CFLPkP0K.mjs → LeafletStaticMap-CvZ3gOdD.mjs} +1 -1
  99. package/dist/client/{checkoutUrls-CXhko2Rv.mjs → RichText-BtfNvQV6.mjs} +2 -29
  100. package/dist/client/{TerminalPanel-JjmgxEsR.mjs → TerminalPanel-CUCNEwlH.mjs} +2 -18
  101. package/dist/client/{ThemedButton-CKV1Jh2m.mjs → ThemedButton-C7WhXuRl.mjs} +2 -2
  102. package/dist/client/{accordion-DXWIfT3T.mjs → accordion-cQm5mjqs.mjs} +1 -1
  103. package/dist/client/{bookingForm-CjZgHNvv.mjs → bookingForm-BVPJE5RQ.mjs} +11 -10
  104. package/dist/client/bookings.mjs +2668 -1134
  105. package/dist/client/{carousel-DSLikmvZ.mjs → carousel-3vgnY7LC.mjs} +2 -2
  106. package/dist/client/{carousel.client-A2lG-9od.mjs → carousel.client-CISsdZvg.mjs} +1 -1
  107. package/dist/client/{cart-HDjWReFt.mjs → cart-BGK4oOjm.mjs} +3 -3
  108. package/dist/client/{checkout-CUvRZA3W.mjs → checkout-GsBLUNJD.mjs} +3 -3
  109. package/dist/client/checkoutUrls-ChxwaNtO.mjs +28 -0
  110. package/dist/client/client.mjs +3042 -1868
  111. package/dist/client/{colorStyles-j5U9v5JA.mjs → colorStyles-D-32xHZh.mjs} +2 -30
  112. package/dist/client/{context-DFmaZa-Q.mjs → context-D_8s12Fp.mjs} +1 -1
  113. package/dist/client/{dates-BBep1JnF.mjs → dates-DeJQbQ9T.mjs} +2 -2
  114. package/dist/client/{eventCalendar-zEobhxo4.mjs → eventCalendar-cmucD4NR.mjs} +10 -10
  115. package/dist/client/{eventCombined-Dp7bz5k-.mjs → eventCombined-ku3E9FTl.mjs} +4 -4
  116. package/dist/client/{eventDetails-FE-zqTt7.mjs → eventDetails-CyKZ61sF.mjs} +5 -5
  117. package/dist/client/{eventListing-BMt5snal.mjs → eventListing-B7yxGIhX.mjs} +4 -4
  118. package/dist/client/{eventRegistration-O6ZxHfOb.mjs → eventRegistration-BoXfSFgZ.mjs} +1403 -857
  119. package/dist/client/{form-DFa1D8vt.mjs → form-CNQOWhJo.mjs} +5 -5
  120. package/dist/client/{headerSection-CmjHLR-N.mjs → headerSection-DahaBJGQ.mjs} +3 -3
  121. package/dist/client/hooks.mjs +123 -64
  122. package/dist/client/{locationMap-BXKP_lGh.mjs → locationMap-BoDjqYlW.mjs} +4 -4
  123. package/dist/client/{media-Cl38iOMI.mjs → media-BFY0y2rb.mjs} +2 -1
  124. package/dist/client/{newsletterForm-CU-HmxGx.mjs → newsletterForm-DtzXDklw.mjs} +4 -4
  125. package/dist/client/{pricing-KEAEsibq.mjs → pricing-DXEh2Lhh.mjs} +1 -1
  126. package/dist/client/{productDetail-Ckp7MGnt.mjs → productDetail-D7cPa-00.mjs} +3 -3
  127. package/dist/client/{productList-D91V0sYa.mjs → productList-D1XMap66.mjs} +3 -3
  128. package/dist/client/{propsDecoder-DZd6RJeA.mjs → propsDecoder-DiIeBopB.mjs} +758 -245
  129. package/dist/client/rendering/client.mjs +4381 -2892
  130. package/dist/client/rendering/islands.mjs +19 -19
  131. package/dist/client/rendering.mjs +3026 -1852
  132. package/dist/client/result-DRW7JWXz.mjs +30 -0
  133. package/dist/client/{routeIslandApiConfig-E1EkhhYY.mjs → routeIslandApiConfig-wzR4lKAY.mjs} +1 -1
  134. package/dist/client/scrollToBlock-C86BYkyV.mjs +17 -0
  135. package/dist/client/{shop-4F2jLQPI.mjs → shop-CYT5P-S5.mjs} +8 -8
  136. package/dist/client/{shop-commerce-store-vicweP2K.mjs → shop-commerce-store-wKhsJQxI.mjs} +1 -1
  137. package/dist/client/{shop-commerce-BbfCJjB3.mjs → shop-commerce-ypBu_dx4.mjs} +9 -9
  138. package/dist/client/{shopShared-DlONJt0T.mjs → shopShared-DFGhYrf2.mjs} +52 -6
  139. package/dist/client/{useEventFilters-COYyL3m7.mjs → useEventFilters-blSbN5o7.mjs} +8 -8
  140. package/dist/preview-next/client/runtime.mjs +3 -3
  141. package/dist/{sdk-runtime-CwvFFjB-.mjs → sdk-runtime-2p8uQk1V.mjs} +1 -1
  142. package/dist/{separator-ByL72zUT.mjs → separator-Cvvzk0Oo.mjs} +1 -1
  143. package/dist/server/components.mjs +4066 -3765
  144. package/dist/server/config-validation.mjs +185 -126
  145. package/dist/server/config.mjs +185 -126
  146. package/dist/server/data.mjs +123 -64
  147. package/dist/server/index.mjs +1 -1
  148. package/dist/server/next.mjs +615 -314
  149. package/dist/server/page-converter.mjs +100 -56
  150. package/dist/server/prebuild.mjs +1 -1
  151. package/dist/server/rendering/server.mjs +4041 -3751
  152. package/dist/server/rendering.mjs +4069 -3768
  153. package/dist/server/routing.mjs +149 -72
  154. package/dist/server/server.mjs +124 -65
  155. package/dist/server/theme-bridge.mjs +1122 -310
  156. package/dist/server/theme.mjs +126 -116
  157. package/dist/spinner-CDo6AkDz.mjs +658 -0
  158. package/dist/styles/index.css +75 -212
  159. package/package.json +1 -1
  160. package/dist/_dts/billing/src/checkout/platform.d.ts +0 -77
  161. package/dist/_dts/billing/src/config/env.d.ts +0 -29
  162. package/dist/_dts/billing/src/stripe/client.d.ts +0 -29
  163. package/dist/_dts/blocks/src/system/runtime/hooks/usePaymentVerification.d.ts +0 -63
  164. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventCheckoutHandoffRestore.d.ts +0 -9
  165. package/dist/client/urlParams-D71VXFGx.mjs +0 -47
@@ -1,24 +1,23 @@
1
1
  "use client";
2
- import { a as buildBookingPaymentSelectionViewModel, c as eventRegistrationErrors, d as planCheckoutPaymentFlow, f as planFlexibleBalanceCreditFirst, i as usePolling, l as parseEventRegistrationErrorReason, n as BookingConsentAgreement, o as resolveBookingPaymentDueNowCents, p as useBookingNowIso, r as StepIndicator$1, s as formatPrice, t as BookingMailingListNote, u as getStripeMinimumChargeRequirement } from "./BookingMailingListNote-Cr3EeTP5.mjs";
3
- import { C as resolveDiscountContext, Cn as formatCurrencyAmount, D as WIZARD_STEPS, E as toRegistrationRequestSubject, M as resolveEventRegistrationScopedEvents, O as getStepNumber, Ot as getRegistrationSuccessCopy, S as findPreSelectedEvent, Sn as publicEventPricingType, T as resolveRegistrationTarget, Wt as assertNever, _ as buildEventRegistrationDisplayFromScopedEvents, b as toWholeSeriesRegistrationEvents, bn as publicEventPricingOptions, en as resolveBookingSignupOffer, fn as toBookingTimeZone, ft as groupPublicEventsByRun, in as formatBookingDateTime, k as getWizardStepsForContext, mn as toScheduledBookingDateTime, nn as formatBookingDate, on as formatBookingTime, t as defineIslandPropsDecoder, v as getRegistrationStartAt, vn as publicEventPricingCurrency, w as resolveEventRequiresPayment, x as findEventByOccurrenceId, xn as publicEventPricingStrategy, y as toRegistrationEvents } from "./propsDecoder-DZd6RJeA.mjs";
4
- import { a as canJoinEventWaitlistOnAnyMode, c as formatTicketTypeRemaining, f as getRemainingPlaces, h as selectMostOpenCapacitySource, i as canJoinEventWaitlist, m as selectEventCapacitySource, o as formatEventRemainingPlaces, p as isEventFull, r as formatEventPriceDisplay, s as formatRemainingPlaces, u as getEventAvailability } from "./pricing-KEAEsibq.mjs";
5
- import { n as useBlockApi, r as BlockApiError, t as BlockApiProvider } from "./context-DFmaZa-Q.mjs";
2
+ import { a as buildBookingPaymentSelectionViewModel, c as useBookingNowIso, i as usePolling, n as BookingConsentAgreement, o as resolveBookingPaymentDueNowCents, r as StepIndicator$1, s as formatPrice, t as BookingMailingListNote } from "./BookingMailingListNote-CYHOufsI.mjs";
3
+ import { $t as parseEventRegistrationErrorReason, An as publicEventPricingOptions, C as resolveDiscountContext, D as getRegistrationStepPresentation, E as toRegistrationRequestSubject, M as resolveEventRegistrationScopedEvents, Mn as publicEventPricingType, Nn as formatCurrencyAmount, O as getStepNumber, On as publicEventPricingCurrency, Ot as getRegistrationSuccessCopy, Qt as eventRegistrationErrors, S as findPreSelectedEvent, Sn as toScheduledBookingDateTime, T as resolveRegistrationTarget, Wt as assertNever, _ as buildEventRegistrationDisplayFromScopedEvents, b as toWholeSeriesRegistrationEvents, bn as toBookingTimeZone, cn as resolveBookingSignupOffer, en as getStripeMinimumChargeRequirement, fn as formatBookingDateTime, ft as groupPublicEventsByRun, jn as publicEventPricingStrategy, k as getWizardStepsForContext, mn as formatBookingTime, nn as planFlexibleBalanceCreditFirst, t as defineIslandPropsDecoder, tn as planCheckoutPaymentFlow, un as formatBookingDate, v as getRegistrationStartAt, w as resolveEventRequiresPayment, x as findEventByOccurrenceId, y as toRegistrationEvents } from "./propsDecoder-DiIeBopB.mjs";
4
+ import { i as SpinnerNode, n as ThemedButtonLink, t as ThemedButton } from "./ThemedButton-C7WhXuRl.mjs";
5
+ import { a as canJoinEventWaitlistOnAnyMode, c as formatTicketTypeRemaining, f as getRemainingPlaces, h as selectMostOpenCapacitySource, i as canJoinEventWaitlist, m as selectEventCapacitySource, o as formatEventRemainingPlaces, p as isEventFull, r as formatEventPriceDisplay, s as formatRemainingPlaces, u as getEventAvailability } from "./pricing-DXEh2Lhh.mjs";
6
+ import { n as useBlockApi, r as BlockApiError, t as BlockApiProvider } from "./context-D_8s12Fp.mjs";
6
7
  import { n as isSpamProtectionEnabled, r as AltchaWidget, t as getChallengeUrl } from "./src-B833rYRr.mjs";
7
- import { a as requestDemoPortalMagicLink, f as storeDemoBookingPass, i as buildPortalCallbackUrl, o as DiscountCodeDisclosure, r as CUSTOMER_PORTAL_PATH, t as BookingConfirmation } from "./DemoBookingPortalAccess-BFGB1Fay.mjs";
8
- import { a as useDiscountCodeUi, i as CheckIcon, n as StateIcon, r as scrollToBlock, t as TerminalPanel } from "./TerminalPanel-JjmgxEsR.mjs";
9
- import { i as SpinnerNode, n as ThemedButtonLink, t as ThemedButton } from "./ThemedButton-CKV1Jh2m.mjs";
8
+ import { a as requestDemoPortalMagicLink, f as storeDemoBookingPass, i as buildPortalCallbackUrl, o as DiscountCodeDisclosure, r as CUSTOMER_PORTAL_PATH, t as BookingConfirmation } from "./DemoBookingPortalAccess-Cvr9O_ca.mjs";
9
+ import { i as useDiscountCodeUi, n as StateIcon, r as CheckIcon, t as TerminalPanel } from "./TerminalPanel-CUCNEwlH.mjs";
10
10
  import { t as FeedbackMessage } from "./FeedbackMessage-CtLvvS_d.mjs";
11
11
  import { i as getFieldValidationControlProps, r as FieldValidationMessage } from "./mailingListSignupCopy-DZSHZrm6.mjs";
12
- import { t as buildCheckoutReturnUrls } from "./checkoutUrls-CXhko2Rv.mjs";
13
- import { t as CheckboxChoice } from "./CheckboxChoice-B33MqoUo.mjs";
12
+ import { t as CheckboxChoice } from "./CheckboxChoice-M-3HycLw.mjs";
14
13
  import { t as createBodyThemePortalRoot } from "./themePortalRoot-AGs4cE7t.mjs";
15
- import { n as consumeSearchParams } from "./urlParams-D71VXFGx.mjs";
16
14
  import { t as CrossIcon } from "./CrossIcon-Dn1KRKW5.mjs";
17
15
  import { t as mountReactIsland } from "./reactMount-SY8AVRof.mjs";
18
- import { t as buildRouteIslandApiConfig } from "./routeIslandApiConfig-E1EkhhYY.mjs";
16
+ import { t as buildRouteIslandApiConfig } from "./routeIslandApiConfig-wzR4lKAY.mjs";
19
17
  import { t as createLucideIcon } from "./createLucideIcon-PaLVMov4.mjs";
20
18
  import { t as ModalShell } from "./ModalShell-CqkBvBIC.mjs";
21
- import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
19
+ import React, { Suspense, lazy, useCallback, useEffect, useId, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
20
+ import { z } from "zod";
22
21
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
22
  import { createPortal } from "react-dom";
24
23
  //#region ../core/src/uuid.ts
@@ -175,6 +174,161 @@ const Wallet = createLucideIcon("wallet", [["path", {
175
174
  key: "xoc0q4"
176
175
  }]]);
177
176
  //#endregion
177
+ //#region ../blocks/src/system/runtime/nodes/event-registration/registrationCompletion.ts
178
+ /** One admission decision for navigation, labels and completion. Email recovery
179
+ * permits navigation only; submission remains the owner of payment recovery. */
180
+ function resolveRegistrationAdmission(input) {
181
+ if (input.hasWaitlistClaim) return { kind: "resume" };
182
+ if (input.eligibilityLoading) return { kind: "checking" };
183
+ if (input.eligibilityError) return {
184
+ kind: "closed",
185
+ reason: "optionsUnavailable",
186
+ message: input.eligibilityError
187
+ };
188
+ if (!input.eligibility) return { kind: "checking" };
189
+ const { registration, availability } = input.eligibility;
190
+ if (registration.kind === "pending_payment") return { kind: "resume" };
191
+ if (registration.kind === "waitlisted") return {
192
+ kind: "closed",
193
+ reason: "alreadyWaitlisted",
194
+ message: "You're already on the waitlist for this event."
195
+ };
196
+ return admissionFromAvailability(availability, input.isAuthenticated);
197
+ }
198
+ function admissionFromAvailability(availability, isAuthenticated) {
199
+ switch (availability.kind) {
200
+ case "available": return { kind: "open" };
201
+ case "waitlist": return {
202
+ kind: "waitlist",
203
+ recovery: isAuthenticated ? "none" : "byEmail"
204
+ };
205
+ case "unavailable": switch (availability.reason) {
206
+ case "full": return {
207
+ kind: "fullyBooked",
208
+ recovery: isAuthenticated ? "none" : "byEmail"
209
+ };
210
+ case "paymentUnavailable": return {
211
+ kind: "closed",
212
+ reason: "unavailable",
213
+ message: "Payment options are not available for this event."
214
+ };
215
+ case "selectionRequired": return { kind: "open" };
216
+ case "bookingClosed": return {
217
+ kind: "closed",
218
+ reason: "unavailable",
219
+ message: "Registration is not available for this event."
220
+ };
221
+ }
222
+ }
223
+ }
224
+ function stepAfterTickets(admission) {
225
+ switch (admission.kind) {
226
+ case "open":
227
+ case "resume":
228
+ case "waitlist": return "enter-details";
229
+ case "fullyBooked": return "fully-booked";
230
+ case "checking":
231
+ case "closed": return "select-tickets";
232
+ }
233
+ }
234
+ function resolveRegistrationAvailabilityBlocker(admission) {
235
+ switch (admission.kind) {
236
+ case "open":
237
+ case "resume":
238
+ case "waitlist": return null;
239
+ case "fullyBooked": return admission.recovery === "none" ? {
240
+ kind: "unavailable",
241
+ message: "This event is fully booked."
242
+ } : null;
243
+ case "checking": return {
244
+ kind: "pending",
245
+ message: "Checking availability..."
246
+ };
247
+ case "closed": return {
248
+ kind: "unavailable",
249
+ message: admission.message
250
+ };
251
+ }
252
+ }
253
+ function resolvePricingSelectionBlocker(input) {
254
+ if (input.selectionRequired && !input.hasValidSelection) return "Choose at least one available ticket to continue.";
255
+ return input.slidingScaleSelectionMessage;
256
+ }
257
+ /** Submit owns fresh admission. Open quotes do not depend on an email lookup. */
258
+ function resolveGuestNextStepCheck(admission, request) {
259
+ switch (admission.kind) {
260
+ case "open":
261
+ case "resume": return {
262
+ kind: "skip",
263
+ outcome: { kind: "continue" }
264
+ };
265
+ case "checking": return {
266
+ kind: "blocked",
267
+ message: "Checking availability..."
268
+ };
269
+ case "closed": return {
270
+ kind: "blocked",
271
+ message: admission.message
272
+ };
273
+ case "fullyBooked":
274
+ if (admission.recovery === "none") return {
275
+ kind: "blocked",
276
+ message: "This event is fully booked."
277
+ };
278
+ return recoveryCheckRequest(request);
279
+ case "waitlist": return admission.recovery === "none" ? {
280
+ kind: "skip",
281
+ outcome: { kind: "waitlist" }
282
+ } : recoveryCheckRequest(request);
283
+ }
284
+ }
285
+ function recoveryCheckRequest(request) {
286
+ return request ? {
287
+ kind: "check",
288
+ request
289
+ } : {
290
+ kind: "blocked",
291
+ message: "Choose an event before checking your booking."
292
+ };
293
+ }
294
+ function resolveRegistrationTicketContinuation(input) {
295
+ if (input.isCheckingExistingRegistration) return {
296
+ kind: "blocked",
297
+ label: "Checking registration...",
298
+ pending: true,
299
+ message: "Your booking options are being checked."
300
+ };
301
+ if (input.ticketSelectionRequired && !input.hasValidTicketSelection) return {
302
+ kind: "blocked",
303
+ label: "Choose a ticket",
304
+ pending: false,
305
+ message: "Choose at least one available ticket to continue."
306
+ };
307
+ switch (input.admission.kind) {
308
+ case "checking": return {
309
+ kind: "blocked",
310
+ label: "Checking availability...",
311
+ pending: true,
312
+ message: "Your booking options are being checked."
313
+ };
314
+ case "closed": return {
315
+ kind: "blocked",
316
+ label: input.admission.reason === "alreadyWaitlisted" ? "Already on waitlist" : input.admission.reason === "optionsUnavailable" ? "Availability unavailable" : "Registration unavailable",
317
+ pending: false,
318
+ message: input.admission.message
319
+ };
320
+ case "open":
321
+ case "resume":
322
+ case "waitlist":
323
+ case "fullyBooked": return {
324
+ kind: "advance",
325
+ admission: input.admission,
326
+ label: "Continue",
327
+ pending: false
328
+ };
329
+ }
330
+ }
331
+ //#endregion
178
332
  //#region ../blocks/src/system/runtime/nodes/event-registration/eventPaymentOptions.ts
179
333
  function getApprovedDirectPaymentCollectionOptionKinds(paymentOptions) {
180
334
  return paymentOptions.flatMap((option) => {
@@ -274,9 +428,6 @@ function resolveEventRegistrationDirectPaymentSelection(input) {
274
428
  }
275
429
  //#endregion
276
430
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationStateMachine.ts
277
- function hasBlockingExistingRegistration(input) {
278
- return !input.hasWaitlistClaim && input.isAuthenticated && input.registration.kind === "waitlisted";
279
- }
280
431
  function createInitialRegistrationFlowState(hasPreSelectedContext) {
281
432
  return {
282
433
  step: hasPreSelectedContext ? "select-tickets" : "select-event",
@@ -352,8 +503,7 @@ function isSelectionValid(input) {
352
503
  function normalizeRegistrationFlowState(state) {
353
504
  const hasPass = Boolean(state.eligibility?.eligiblePasses.length);
354
505
  const hasMembership = Boolean(state.eligibility?.eligibleMemberships.length);
355
- let nextPassOptOut = state.passOptOut;
356
- if (nextPassOptOut && (state.step === "select-tickets" || state.ticketsCount !== 1 || !hasPass || hasMembership)) nextPassOptOut = false;
506
+ const nextPassOptOut = state.passOptOut && !(state.step === "select-tickets" || state.ticketsCount !== 1 || !hasPass || hasMembership);
357
507
  const autoSelection = computeAutoSelection({
358
508
  isAuthenticated: state.isAuthenticated,
359
509
  eligibility: state.eligibility,
@@ -378,8 +528,27 @@ function normalizeRegistrationFlowState(state) {
378
528
  };
379
529
  }
380
530
  function registrationFlowReducer(state, action) {
531
+ if ([
532
+ "payment",
533
+ "submitting",
534
+ "verifying-payment",
535
+ "verifying-timeout",
536
+ "payment-status"
537
+ ].includes(state.step) && action.type !== "set-step") return state;
381
538
  switch (action.type) {
382
- case "set-step": return normalizeRegistrationFlowState({
539
+ case "admission-updated":
540
+ case "tickets-completed":
541
+ case "recovery-checked": return reduceRegistrationAdmission(state, action);
542
+ case "set-step": return [
543
+ "payment",
544
+ "submitting",
545
+ "verifying-payment",
546
+ "verifying-timeout",
547
+ "payment-status"
548
+ ].includes(action.step) ? {
549
+ ...state,
550
+ step: action.step
551
+ } : normalizeRegistrationFlowState({
383
552
  ...state,
384
553
  step: action.step
385
554
  });
@@ -395,18 +564,8 @@ function registrationFlowReducer(state, action) {
395
564
  ...state,
396
565
  eligibility: action.eligibility
397
566
  });
398
- case "set-manual-payment-selection": return normalizeRegistrationFlowState({
399
- ...state,
400
- paymentSelection: normalizePaymentSelection(action.selection),
401
- selectionSource: "manual",
402
- passOptOut: action.selection.method === "stripe" ? true : state.passOptOut
403
- });
404
- case "use-card-instead-of-pass": return normalizeRegistrationFlowState({
405
- ...state,
406
- passOptOut: true,
407
- paymentSelection: computeDirectPaymentSelection(state.eligibility),
408
- selectionSource: "manual"
409
- });
567
+ case "set-manual-payment-selection":
568
+ case "use-card-instead-of-pass": return reduceRegistrationPaymentChoice(state, action);
410
569
  case "event-changed": return normalizeRegistrationFlowState({
411
570
  ...state,
412
571
  step: action.hasPreSelectedContext ? "select-tickets" : "select-event",
@@ -416,7 +575,6 @@ function registrationFlowReducer(state, action) {
416
575
  eligibility: null
417
576
  });
418
577
  case "reset": return createInitialRegistrationFlowState(action.hasPreSelectedContext);
419
- default: return state;
420
578
  }
421
579
  }
422
580
  function isEntitlementSelection(selection) {
@@ -426,6 +584,38 @@ function requiresPaymentStep(params) {
426
584
  if (!params.eventRequiresPayment) return false;
427
585
  return !isEntitlementSelection(params.paymentSelection);
428
586
  }
587
+ function reduceRegistrationPaymentChoice(state, action) {
588
+ switch (action.type) {
589
+ case "set-manual-payment-selection": return normalizeRegistrationFlowState({
590
+ ...state,
591
+ paymentSelection: normalizePaymentSelection(action.selection),
592
+ selectionSource: "manual",
593
+ passOptOut: action.selection.method === "stripe" ? true : state.passOptOut
594
+ });
595
+ case "use-card-instead-of-pass": return normalizeRegistrationFlowState({
596
+ ...state,
597
+ passOptOut: true,
598
+ paymentSelection: computeDirectPaymentSelection(state.eligibility),
599
+ selectionSource: "manual"
600
+ });
601
+ }
602
+ }
603
+ function reduceRegistrationAdmission(state, action) {
604
+ switch (action.type) {
605
+ case "admission-updated": return state.step === "fully-booked" && action.admission.kind !== "checking" ? normalizeRegistrationFlowState({
606
+ ...state,
607
+ step: stepAfterTickets(action.admission)
608
+ }) : state;
609
+ case "tickets-completed": return state.step === "select-tickets" ? normalizeRegistrationFlowState({
610
+ ...state,
611
+ step: stepAfterTickets(action.admission)
612
+ }) : state;
613
+ case "recovery-checked": return state.step === "fully-booked" && action.outcome.kind !== "unavailable" ? normalizeRegistrationFlowState({
614
+ ...state,
615
+ step: "enter-details"
616
+ }) : state;
617
+ }
618
+ }
429
619
  //#endregion
430
620
  //#region ../blocks/src/system/runtime/nodes/event-registration/event-registration.headless.planning.ts
431
621
  function isEventRegistrationAvailable(event) {
@@ -767,16 +957,66 @@ const EventSelectionStep = ({ events, selectedEvent, onSelect, showVenue, showCa
767
957
  });
768
958
  };
769
959
  //#endregion
960
+ //#region ../blocks/src/system/runtime/nodes/event-registration/useRegistrationAdmission.ts
961
+ /** Owns admission and its navigation commands; renderers receive decisions. */
962
+ function useRegistrationAdmission(input) {
963
+ const { eligibility, eligibilityError, eligibilityLoading, hasWaitlistClaim, isAuthenticated, dispatchFlow } = input;
964
+ const admission = useMemo(() => resolveRegistrationAdmission({
965
+ eligibility,
966
+ eligibilityError,
967
+ eligibilityLoading,
968
+ hasWaitlistClaim,
969
+ isAuthenticated
970
+ }), [
971
+ eligibility,
972
+ eligibilityError,
973
+ eligibilityLoading,
974
+ hasWaitlistClaim,
975
+ isAuthenticated
976
+ ]);
977
+ useEffect(() => {
978
+ dispatchFlow({
979
+ type: "admission-updated",
980
+ admission
981
+ });
982
+ }, [admission, dispatchFlow]);
983
+ const ticketContinuation = resolveRegistrationTicketContinuation({
984
+ admission,
985
+ ticketSelectionRequired: input.ticketSelectionRequired,
986
+ hasValidTicketSelection: input.hasValidTicketSelection,
987
+ isCheckingExistingRegistration: isAuthenticated && input.existingRegistrationLoading
988
+ });
989
+ const handleTicketsComplete = () => {
990
+ if (ticketContinuation.kind === "blocked") return {
991
+ ok: false,
992
+ reason: "blocked",
993
+ message: ticketContinuation.message
994
+ };
995
+ dispatchFlow({
996
+ type: "tickets-completed",
997
+ admission: ticketContinuation.admission
998
+ });
999
+ return { ok: true };
1000
+ };
1001
+ const onRecoveryChecked = (outcome) => dispatchFlow({
1002
+ type: "recovery-checked",
1003
+ outcome
1004
+ });
1005
+ return {
1006
+ admission,
1007
+ ticketContinuation,
1008
+ handleTicketsComplete,
1009
+ onRecoveryChecked
1010
+ };
1011
+ }
1012
+ //#endregion
770
1013
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationErrorRecovery.ts
771
1014
  function resolveRegistrationRecoveryAction(step, action) {
772
1015
  if (step === "payment-failed") return "payment-options";
773
1016
  if (step === "payment-status") return "none";
774
- if (step === "verifying-payment" || step === "verifying-timeout" || step === "redirecting") return "none";
1017
+ if (step === "verifying-payment" || step === "verifying-timeout" || step === "payment") return "none";
775
1018
  return action;
776
1019
  }
777
- function canRecoverToWaitlist(eligibility) {
778
- return eligibility?.availability.kind === "waitlist" && eligibility.registration.kind !== "pending_payment";
779
- }
780
1020
  function shouldRefreshAfterRegistrationError(step, action, enabled) {
781
1021
  return enabled || step === "error" && action === "places";
782
1022
  }
@@ -790,7 +1030,6 @@ function resetRegistrationAfterExpiry(input) {
790
1030
  const mode = input.preSelectedEvent?.deliveryMode;
791
1031
  input.setSelectedAttendanceMode(mode === "online" || mode === "in_person" ? mode : null);
792
1032
  input.setErrorMessage("");
793
- input.setPendingRegistrationReference(null);
794
1033
  }
795
1034
  /** Retry the retained submission in place; explicit payment uncertainty stays status-only. */
796
1035
  function recoverRegistrationError(action, handlers) {
@@ -1290,441 +1529,174 @@ function useResolvedWaitlistClaim(deps, credential) {
1290
1529
  };
1291
1530
  }
1292
1531
  //#endregion
1293
- //#region ../blocks/src/system/runtime/hooks/usePaymentVerification.ts
1294
- /**
1295
- * Hook for polling payment status after Stripe redirects.
1296
- *
1297
- * Built on top of the generic usePolling hook, this provides
1298
- * a specialized interface for verifying event registration payments.
1299
- *
1300
- * @example
1301
- * ```tsx
1302
- * const verification = usePaymentVerification(
1303
- * step === 'verifying-payment' && attendeeId
1304
- * ? { transactionId: attendeeId }
1305
- * : null
1306
- * );
1307
- *
1308
- * useEffect(() => {
1309
- * if (verification.status === 'completed') {
1310
- * setStep('success');
1311
- * } else if (verification.status === 'failed') {
1312
- * setStep('payment-failed');
1313
- * } else if (verification.status === 'timeout') {
1314
- * setStep('verifying-timeout');
1315
- * }
1316
- * }, [verification.status]);
1317
- * ```
1318
- */
1319
- const paymentVerificationDeps = { useBlockApi };
1320
- function resolvePaymentPollingTarget(config) {
1321
- const reference = config.reference;
1322
- return reference.scope === "occurrence" ? {
1323
- key: `occurrence:${reference.attendeeId}`,
1324
- reference
1325
- } : {
1326
- key: `wholeSeries:${reference.registrationId}`,
1327
- reference
1328
- };
1329
- }
1330
- function createPaymentPollingConfig(api, settings) {
1331
- if (settings === null) return null;
1332
- const target = resolvePaymentPollingTarget(settings);
1333
- return {
1334
- restartKey: target.key,
1335
- pollFn: () => api.getEventRegistrationStatus(target.reference),
1336
- isComplete: (result) => result.paymentStatus === "completed" || result.paymentStatus === "not_required",
1337
- isFailed: (result) => result.paymentStatus === "failed" || result.paymentStatus === "refunded",
1338
- maxAttempts: settings.maxAttempts,
1339
- interval: settings.pollInterval
1340
- };
1341
- }
1342
- function getPaymentVerificationError(polling, paymentStatus) {
1343
- if (polling.status === "timeout") return "Could not verify payment. If you were charged, please contact support.";
1344
- if (polling.status === "failed" && paymentStatus !== null) return paymentStatus === "refunded" ? "Payment was refunded. Please contact support." : "Payment was declined. Please try again.";
1345
- return polling.error?.message ?? null;
1346
- }
1347
- function mapPaymentVerificationResult(polling) {
1348
- const paymentStatus = polling.result?.paymentStatus ?? null;
1349
- return {
1350
- status: polling.status,
1351
- paymentStatus,
1352
- attendeeStatus: polling.result?.attendeeStatus ?? null,
1353
- waitlistPosition: polling.result?.waitlistPosition ?? null,
1354
- error: getPaymentVerificationError(polling, paymentStatus),
1355
- retry: polling.retry
1356
- };
1357
- }
1358
- /**
1359
- * Hook for polling payment status after Stripe redirects.
1360
- *
1361
- * Pass config to start polling, or null to stop/idle.
1362
- * The hook handles cleanup on unmount and starts a new cycle when the status
1363
- * reference changes.
1364
- *
1365
- * @param config - Configuration for polling, or null to stop
1366
- * @returns Verification result with status, data, and retry function
1367
- */
1368
- function usePaymentVerification(config) {
1369
- return usePaymentVerificationWithDeps(config, paymentVerificationDeps);
1370
- }
1371
- function usePaymentVerificationWithDeps(config, deps) {
1372
- return mapPaymentVerificationResult(usePolling(createPaymentPollingConfig(deps.useBlockApi(), config)));
1373
- }
1374
- //#endregion
1375
- //#region ../blocks/src/system/runtime/nodes/event-registration/constants.ts
1376
- const EVENT_REGISTRATION_ANCHOR_ID = "event-registration";
1377
- const EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM = "registration_attempt";
1378
- //#endregion
1379
1532
  //#region ../blocks/src/system/runtime/nodes/event-registration/sessionStorage.ts
1380
1533
  const STORAGE_PREFIX = "riverbank:event-registration:";
1381
1534
  const STALE_THRESHOLD_MS = 1440 * 60 * 1e3;
1382
- /**
1383
- * Build the sessionStorage key for a specific registration.
1384
- */
1385
- function getStorageKey(siteId, checkoutAttemptId) {
1386
- return `${STORAGE_PREFIX}${siteId}:${checkoutAttemptId}`;
1387
- }
1388
- /**
1389
- * Check if sessionStorage is available.
1390
- * Returns false in private browsing mode or when storage is disabled.
1391
- */
1392
- function isStorageAvailable() {
1393
- try {
1394
- const testKey = "__riverbank_test__";
1395
- sessionStorage.setItem(testKey, "test");
1396
- sessionStorage.removeItem(testKey);
1397
- return true;
1398
- } catch {
1399
- return false;
1400
- }
1401
- }
1402
- /**
1403
- * Save pending registration data before Stripe redirect.
1404
- *
1405
- * @param siteId - Site ID
1406
- * @param checkoutAttemptId - Correlation ID included in the checkout return URL
1407
- * @param data - Registration data to save (excluding version and timestamp)
1408
- */
1409
- function savePendingRegistration(siteId, checkoutAttemptId, data) {
1410
- if (!isStorageAvailable()) {
1411
- console.warn("[sessionStorage] Storage not available, skipping save");
1412
- return;
1413
- }
1535
+ const paymentSelection = z.discriminatedUnion("method", [
1536
+ z.object({ method: z.literal("free") }),
1537
+ z.object({
1538
+ method: z.literal("stripe"),
1539
+ collectionOptionKind: z.enum([
1540
+ "upfront",
1541
+ "deferred_manual",
1542
+ "instalment_plan",
1543
+ "flexible_balance"
1544
+ ])
1545
+ }),
1546
+ z.object({
1547
+ method: z.literal("pass"),
1548
+ passId: z.string(),
1549
+ productName: z.string().optional()
1550
+ }),
1551
+ z.object({
1552
+ method: z.literal("membership"),
1553
+ membershipId: z.string(),
1554
+ productName: z.string().optional()
1555
+ }),
1556
+ z.object({
1557
+ method: z.literal("pass_bundle"),
1558
+ bundlePassId: z.string(),
1559
+ productName: z.string().optional(),
1560
+ productDescription: z.string().optional(),
1561
+ productPrice: z.number().optional(),
1562
+ productCurrency: z.string().optional()
1563
+ }),
1564
+ z.object({
1565
+ method: z.literal("membership_bundle"),
1566
+ bundleMembershipId: z.string(),
1567
+ productName: z.string().optional(),
1568
+ productDescription: z.string().optional(),
1569
+ productPrice: z.number().optional(),
1570
+ productCurrency: z.string().optional(),
1571
+ productInterval: z.string().optional()
1572
+ })
1573
+ ]);
1574
+ const schema = z.object({
1575
+ version: z.literal(6),
1576
+ reference: z.discriminatedUnion("scope", [z.object({
1577
+ scope: z.literal("occurrence"),
1578
+ attendeeId: z.string()
1579
+ }), z.object({
1580
+ scope: z.literal("wholeSeries"),
1581
+ registrationId: z.string()
1582
+ })]),
1583
+ formData: z.object({
1584
+ name: z.string(),
1585
+ email: z.string(),
1586
+ phone: z.string(),
1587
+ notes: z.string(),
1588
+ consentAccepted: z.boolean().optional(),
1589
+ mailingListOptIn: z.boolean().optional()
1590
+ }),
1591
+ ticketSelections: z.array(z.object({
1592
+ pricingOptionId: z.string().nullable(),
1593
+ quantity: z.number().int().positive()
1594
+ })).min(1),
1595
+ selectedEventId: z.string().nullable(),
1596
+ selectedAttendanceMode: z.enum(["in_person", "online"]).nullable().optional(),
1597
+ paymentSelection,
1598
+ guests: z.array(z.object({
1599
+ name: z.string(),
1600
+ email: z.string().nullable(),
1601
+ phone: z.string().nullable()
1602
+ })),
1603
+ selectedSlidingAmount: z.number().nullable(),
1604
+ discountCode: z.string(),
1605
+ mailingListSignupNote: z.enum(["confirmation_required", "not_confirmed"]).optional(),
1606
+ timestamp: z.number().finite()
1607
+ });
1608
+ function key(siteId, submissionId) {
1609
+ return `${STORAGE_PREFIX}${siteId}:${submissionId}`;
1610
+ }
1611
+ function savePendingRegistration(siteId, submissionId, data) {
1414
1612
  try {
1415
- const key = getStorageKey(siteId, checkoutAttemptId);
1416
- const ticketSelections = normalizeTicketSelectionLines(data.ticketSelections);
1417
- if (ticketSelections.length === 0) {
1418
- console.warn("[sessionStorage] No ticket selections to save, skipping registration state");
1419
- return;
1420
- }
1421
- const entry = {
1613
+ const entry = schema.safeParse({
1422
1614
  ...data,
1423
- ticketSelections,
1424
- version: 5,
1615
+ ticketSelections: normalizeTicketSelectionLines(data.ticketSelections),
1616
+ version: 6,
1425
1617
  timestamp: Date.now()
1426
- };
1427
- sessionStorage.setItem(key, JSON.stringify(entry));
1428
- } catch (error) {
1429
- console.warn("[sessionStorage] Failed to save registration:", error);
1430
- }
1431
- }
1432
- /**
1433
- * Get pending registration data after returning from Stripe.
1434
- *
1435
- * Returns null if:
1436
- * - No entry exists for this checkout attempt
1437
- * - Entry is stale (> 24 hours old)
1438
- * - Entry is corrupt or wrong version
1439
- * - Storage is unavailable
1440
- *
1441
- * @param siteId - Site ID
1442
- * @param checkoutAttemptId - Correlation ID returned from checkout.
1443
- * @returns Pending registration data or null
1444
- */
1445
- function getPendingRegistration(siteId, checkoutAttemptId) {
1446
- if (!isStorageAvailable()) return null;
1447
- try {
1448
- const key = getStorageKey(siteId, checkoutAttemptId);
1449
- const stored = sessionStorage.getItem(key);
1450
- if (stored) return parseAndValidateEntry(stored, key);
1451
- return null;
1452
- } catch (error) {
1453
- console.warn("[sessionStorage] Failed to read registration:", error);
1454
- return null;
1455
- }
1618
+ });
1619
+ if (entry.success) sessionStorage.setItem(key(siteId, submissionId), JSON.stringify(entry.data));
1620
+ } catch {}
1456
1621
  }
1457
- /**
1458
- * Internal helper to parse and validate a storage entry.
1459
- */
1460
- function parseAndValidateEntry(stored, key) {
1622
+ function getPendingRegistration(siteId, submissionId) {
1461
1623
  try {
1462
- const raw = JSON.parse(stored);
1463
- if (!raw || typeof raw !== "object") {
1464
- sessionStorage.removeItem(key);
1465
- return null;
1466
- }
1467
- const version = raw.version;
1468
- if (version !== 4 && version !== 5) {
1469
- console.warn("[sessionStorage] Outdated version, ignoring entry");
1470
- sessionStorage.removeItem(key);
1471
- return null;
1472
- }
1473
- const entry = normalizeStoredRegistration(raw);
1474
- const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
1475
- if (!Number.isFinite(timestamp)) {
1476
- sessionStorage.removeItem(key);
1477
- return null;
1478
- }
1479
- if (Date.now() - timestamp > STALE_THRESHOLD_MS) {
1480
- console.info("[sessionStorage] Entry is stale, removing");
1481
- sessionStorage.removeItem(key);
1482
- return null;
1483
- }
1484
- const ticketSelections = normalizeTicketSelectionLines(entry.ticketSelections);
1485
- if (ticketSelections.length === 0 || getTicketSelectionTotal(ticketSelections) <= 0) {
1486
- sessionStorage.removeItem(key);
1624
+ const stored = sessionStorage.getItem(key(siteId, submissionId));
1625
+ const parsed = schema.safeParse(stored ? JSON.parse(stored) : null);
1626
+ if (!parsed.success || Date.now() - parsed.data.timestamp > STALE_THRESHOLD_MS) {
1627
+ clearPendingRegistration(siteId, submissionId);
1487
1628
  return null;
1488
1629
  }
1489
- return {
1490
- ...entry,
1491
- ticketSelections
1492
- };
1630
+ return parsed.data;
1493
1631
  } catch {
1494
- console.warn("[sessionStorage] Failed to parse entry, removing");
1495
- sessionStorage.removeItem(key);
1496
1632
  return null;
1497
1633
  }
1498
1634
  }
1499
- function normalizeStoredRegistration(entry) {
1500
- if (entry.version === 5) return entry;
1501
- const { attendeeId, ...stored } = entry;
1502
- return {
1503
- ...stored,
1504
- version: 5,
1505
- reference: {
1506
- scope: "occurrence",
1507
- attendeeId
1508
- }
1509
- };
1510
- }
1511
- /**
1512
- * Clear pending registration data after successful verification.
1513
- *
1514
- * @param siteId - Site ID
1515
- * @param checkoutAttemptId - Checkout return correlation ID
1516
- */
1517
- function clearPendingRegistration(siteId, checkoutAttemptId) {
1518
- if (!isStorageAvailable()) return;
1635
+ function clearPendingRegistration(siteId, submissionId) {
1519
1636
  try {
1520
- const key = getStorageKey(siteId, checkoutAttemptId);
1521
- sessionStorage.removeItem(key);
1637
+ sessionStorage.removeItem(key(siteId, submissionId));
1522
1638
  } catch {}
1523
1639
  }
1524
- /**
1525
- * Clean up all stale registration entries from sessionStorage.
1526
- * Should be called on component mount to prevent accumulation.
1527
- */
1528
1640
  function cleanupStaleRegistrations() {
1529
- if (!isStorageAvailable()) return;
1530
1641
  try {
1531
- const keysToRemove = [];
1532
- for (let i = 0; i < sessionStorage.length; i++) {
1533
- const key = sessionStorage.key(i);
1534
- if (!key?.startsWith(STORAGE_PREFIX)) continue;
1642
+ Array.from({ length: sessionStorage.length }, (_, index) => sessionStorage.key(index)).filter((item) => item !== null && item.startsWith(STORAGE_PREFIX)).forEach((item) => {
1535
1643
  try {
1536
- const stored = sessionStorage.getItem(key);
1537
- if (!stored) continue;
1538
- const entry = JSON.parse(stored);
1539
- const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
1540
- const age = Date.now() - timestamp;
1541
- if (!Number.isFinite(timestamp) || age > STALE_THRESHOLD_MS) keysToRemove.push(key);
1644
+ const parsed = schema.safeParse(JSON.parse(sessionStorage.getItem(item) ?? "null"));
1645
+ if (!parsed.success || Date.now() - parsed.data.timestamp > STALE_THRESHOLD_MS) sessionStorage.removeItem(item);
1542
1646
  } catch {
1543
- keysToRemove.push(key);
1647
+ sessionStorage.removeItem(item);
1544
1648
  }
1545
- }
1546
- for (const key of keysToRemove) sessionStorage.removeItem(key);
1547
- if (keysToRemove.length > 0) console.info(`[sessionStorage] Cleaned up ${keysToRemove.length} stale entries`);
1548
- } catch (error) {
1549
- console.warn("[sessionStorage] Cleanup failed:", error);
1550
- }
1649
+ });
1650
+ } catch {}
1551
1651
  }
1552
1652
  //#endregion
1653
+ //#region ../blocks/src/system/runtime/nodes/event-registration/constants.ts
1654
+ const EVENT_REGISTRATION_ANCHOR_ID = "event-registration";
1655
+ const EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM = "registration_attempt";
1656
+ //#endregion
1553
1657
  //#region ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.ts
1554
- const eventRegistrationStripeReturnDeps = {
1555
- usePaymentVerification,
1556
- readReturnParams: () => {
1557
- const params = new URLSearchParams(window.location.search);
1558
- return {
1559
- registration: params.get("registration"),
1560
- registration_attempt: params.get(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM),
1561
- session_id: params.get("session_id")
1562
- };
1563
- },
1564
- clearReturnParams: () => {
1565
- consumeSearchParams([
1566
- "registration",
1567
- EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM,
1568
- "session_id"
1569
- ]);
1570
- },
1571
- scrollToBlock,
1572
- cleanupStaleRegistrations,
1573
- clearPendingRegistration,
1574
- getPendingRegistration
1575
- };
1576
- function restorePendingPresentation(options, pending) {
1577
- options.setFormData(pending.formData);
1578
- options.setTicketSelections(pending.ticketSelections);
1579
- options.setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
1580
- if (pending.selectedEventId) {
1581
- const event = options.events.find((item) => item.selectionId === pending.selectedEventId) ?? null;
1582
- if (event) options.setSelectedEvent(event);
1583
- }
1584
- options.setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
1585
- options.setPaymentSelection(pending.paymentSelection);
1586
- }
1587
- function showReturnReconciliation(options, message) {
1588
- options.setErrorMessage(message);
1589
- options.setStep("verifying-timeout");
1590
- }
1591
- function checkoutReturnCommand(params) {
1592
- const checkoutAttemptId = params.registration_attempt;
1593
- if (!checkoutAttemptId) return null;
1594
- if (params.registration === "cancelled") return {
1595
- kind: "abandon",
1596
- checkoutAttemptId
1597
- };
1598
- if (params.registration === "success" && params.session_id) return {
1599
- kind: "resolveSuccess",
1600
- checkoutAttemptId,
1601
- sessionId: params.session_id
1602
- };
1603
- return null;
1604
- }
1605
- function handleCheckoutReturnOutcome(options, deps, context, outcome) {
1606
- const { pending, params } = context;
1607
- if (outcome.kind === "resolved") {
1608
- deps.clearReturnParams();
1609
- options.setPendingRegistrationReference(outcome.reference);
1610
- options.setStep("verifying-payment");
1611
- return "settled";
1612
- }
1613
- if (outcome.kind === "abandoned") {
1614
- deps.clearReturnParams();
1615
- options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
1616
- options.setStep("select-payment");
1617
- return "settled";
1618
- }
1619
- if (outcome.kind === "notFound" && params.registration === "cancelled" && (options.registrationSubject?.kind === "occurrence" || pending?.reference.scope === "occurrence")) {
1620
- deps.clearReturnParams();
1621
- options.setErrorMessage("Your payment was cancelled. You can choose a payment option and try again.");
1622
- options.setStep("select-payment");
1623
- return "settled";
1624
- }
1625
- showReturnReconciliation(options, "We’re checking your booking and payment. Please don’t try to book again yet. If you’ve already paid, you don’t need to pay again.");
1626
- return outcome.kind === "notFound" ? "settled" : "retryable";
1627
- }
1628
- async function handleCheckoutReturn(options, deps, returnContextRef) {
1629
- deps.cleanupStaleRegistrations();
1630
- const params = deps.readReturnParams();
1631
- if (params.registration !== "cancelled" && params.registration !== "success") return "settled";
1632
- deps.scrollToBlock(EVENT_REGISTRATION_ANCHOR_ID);
1633
- const siteId = options.api.config.siteId;
1634
- const command = checkoutReturnCommand(params);
1635
- if (!siteId) return "settled";
1636
- if (!command) {
1637
- showReturnReconciliation(options, "We’re checking your booking and payment. Please don’t try to book again yet. If you’ve already paid, you don’t need to pay again.");
1638
- return "settled";
1639
- }
1640
- const { checkoutAttemptId } = command;
1641
- const pending = deps.getPendingRegistration(siteId, checkoutAttemptId);
1642
- returnContextRef.current = {
1643
- checkoutAttemptId,
1644
- mailingListSignupNote: pending?.mailingListSignupNote
1645
- };
1646
- if (pending) restorePendingPresentation(options, pending);
1647
- options.setStep("verifying-payment");
1648
- try {
1649
- const outcome = await options.api.resolveEventCheckoutReturn(command);
1650
- return handleCheckoutReturnOutcome(options, deps, {
1651
- pending,
1652
- params
1653
- }, outcome);
1654
- } catch {
1655
- showReturnReconciliation(options, "We couldn’t check your payment just now. Please check again in a moment. Don’t make another booking while we’re checking.");
1656
- }
1657
- return "retryable";
1658
- }
1659
1658
  function useEventRegistrationStripeReturn(options) {
1660
- return useEventRegistrationStripeReturnWithDeps(options, eventRegistrationStripeReturnDeps);
1661
- }
1662
- function completeVerifiedRegistration(options, deps, returnContext) {
1663
- const siteId = options.api.config.siteId;
1664
- const checkoutAttemptId = returnContext?.checkoutAttemptId;
1665
- if (siteId && checkoutAttemptId) deps.clearPendingRegistration(siteId, checkoutAttemptId);
1666
- options.setRegistrationResult({
1667
- status: "confirmed",
1668
- mailingListSignupNote: returnContext?.mailingListSignupNote
1669
- });
1670
- options.setStep("success");
1671
- }
1672
- function useEventRegistrationStripeReturnWithDeps(options, deps) {
1673
- const { api, pendingRegistrationReference, setErrorMessage, setRegistrationResult, setStep, step } = options;
1674
- const returnContextRef = useRef(null);
1675
- const lookupInFlightRef = useRef(false);
1676
- const [lookupState, setLookupState] = useState("settled");
1677
- const resolveReturn = useCallback(async () => {
1678
- if (lookupInFlightRef.current) return;
1679
- lookupInFlightRef.current = true;
1680
- setLookupState("loading");
1681
- try {
1682
- setLookupState(await handleCheckoutReturn(options, deps, returnContextRef));
1683
- } finally {
1684
- lookupInFlightRef.current = false;
1685
- }
1686
- }, [options, deps]);
1687
- const verificationConfig = useMemo(() => step === "verifying-payment" && pendingRegistrationReference ? { reference: pendingRegistrationReference } : null, [step, pendingRegistrationReference]);
1688
- const verification = deps.usePaymentVerification(verificationConfig);
1689
- const hasVerificationTimeout = step === "verifying-timeout" && pendingRegistrationReference !== null;
1690
- const retry = useCallback(() => {
1691
- if (hasVerificationTimeout) {
1692
- setStep("verifying-payment");
1693
- return;
1694
- }
1695
- verification.retry();
1696
- }, [
1697
- hasVerificationTimeout,
1698
- setStep,
1699
- verification.retry
1700
- ]);
1701
- useEffect(() => {
1702
- resolveReturn();
1703
- }, []);
1659
+ const restored = useRef(false);
1704
1660
  useEffect(() => {
1705
- if (verification.status === "completed") completeVerifiedRegistration(options, deps, returnContextRef.current);
1706
- else if (verification.status === "failed") {
1707
- setErrorMessage("Please try again or choose another payment option. If this keeps happening, contact the organiser for help.");
1708
- setStep("payment-failed");
1709
- } else if (verification.status === "timeout") setStep("verifying-timeout");
1710
- }, [
1711
- verification.status,
1712
- verification.attendeeStatus,
1713
- verification.waitlistPosition,
1714
- verification.error,
1715
- api.config.siteId,
1716
- setErrorMessage,
1717
- setRegistrationResult,
1718
- setStep
1719
- ]);
1720
- return {
1721
- ...verification,
1722
- status: hasVerificationTimeout ? "timeout" : verification.status,
1723
- retry,
1724
- retryReturn: lookupState === "retryable" ? () => {
1725
- resolveReturn();
1726
- } : void 0
1727
- };
1661
+ if (restored.current) return;
1662
+ restored.current = true;
1663
+ cleanupStaleRegistrations();
1664
+ const params = new URLSearchParams(window.location.search);
1665
+ const checkoutAttemptId = params.get(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM);
1666
+ const sessionId = params.get("session_id");
1667
+ if (params.get("registration") !== "payment" || !checkoutAttemptId || !sessionId) return;
1668
+ const pending = options.siteId ? getPendingRegistration(options.siteId, checkoutAttemptId) : null;
1669
+ if (pending) options.restore(pending);
1670
+ options.verify({
1671
+ kind: "resolveSession",
1672
+ checkoutAttemptId,
1673
+ sessionId
1674
+ });
1675
+ }, [options]);
1676
+ }
1677
+ //#endregion
1678
+ //#region ../blocks/src/system/runtime/nodes/event-registration/paymentLocation.ts
1679
+ function eventPaymentReturnUrl(submissionId) {
1680
+ const url = new URL(window.location.href);
1681
+ url.searchParams.set("registration", "payment");
1682
+ url.searchParams.set(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM, submissionId);
1683
+ url.searchParams.delete("session_id");
1684
+ url.hash = EVENT_REGISTRATION_ANCHOR_ID;
1685
+ return url.toString();
1686
+ }
1687
+ function rememberEventPayment(submissionId, sessionId) {
1688
+ const url = new URL(eventPaymentReturnUrl(submissionId));
1689
+ url.searchParams.set("session_id", sessionId);
1690
+ window.history.replaceState(window.history.state, "", url);
1691
+ }
1692
+ function clearEventPayment(siteId) {
1693
+ const url = new URL(window.location.href);
1694
+ const submissionId = url.searchParams.get(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM);
1695
+ if (siteId && submissionId) clearPendingRegistration(siteId, submissionId);
1696
+ url.searchParams.delete("registration");
1697
+ url.searchParams.delete(EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM);
1698
+ url.searchParams.delete("session_id");
1699
+ window.history.replaceState(window.history.state, "", url);
1728
1700
  }
1729
1701
  //#endregion
1730
1702
  //#region ../blocks/src/system/runtime/nodes/event-registration/registrationErrorPresentation.ts
@@ -1771,7 +1743,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
1771
1743
  */
1772
1744
  const { commandRef } = options;
1773
1745
  const inFlightRef = useRef(null);
1774
- const submit = async (method, passId, membershipId, selection, submitOptions) => {
1746
+ const submit = async (method, passId, membershipId, selection) => {
1775
1747
  const target = options.registrationTarget;
1776
1748
  if (!target) return {
1777
1749
  ok: false,
@@ -1781,25 +1753,21 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
1781
1753
  options.setStep("submitting");
1782
1754
  const registrationCommandId = commandRef.current?.id ?? deps.createClientCommandId();
1783
1755
  try {
1784
- let prepared = prepareSubmitContext(options, registrationCommandId, target, method, passId, membershipId, selection);
1785
- let request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
1786
- const intentKey = registrationRequestIntentKey(request);
1787
- if (commandRef.current !== null && commandRef.current.intentKey !== intentKey) {
1788
- prepared = {
1789
- ...prepared,
1790
- registrationCommandId: deps.createClientCommandId()
1791
- };
1792
- request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
1793
- }
1756
+ const initialPrepared = prepareSubmitContext(options, registrationCommandId, target, method, passId, membershipId, selection);
1757
+ const intentKey = registrationRequestIntentKey(buildRegistrationRequest(options, initialPrepared, method, passId, membershipId));
1758
+ const prepared = commandRef.current !== null && commandRef.current.intentKey !== intentKey ? {
1759
+ ...initialPrepared,
1760
+ registrationCommandId: deps.createClientCommandId()
1761
+ } : initialPrepared;
1762
+ const request = buildRegistrationRequest(options, prepared, method, passId, membershipId);
1794
1763
  commandRef.current = {
1795
1764
  id: prepared.registrationCommandId,
1796
1765
  intentKey
1797
1766
  };
1798
- const result = await options.api.registerForEvent({
1767
+ return await completeRegistrationSubmission(options, deps, prepared, await options.api.registerForEvent({
1799
1768
  ...request,
1800
1769
  registrationCommandId: prepared.registrationCommandId
1801
- });
1802
- return await completeRegistrationSubmission(options, deps, prepared, result, submitOptions);
1770
+ }));
1803
1771
  } catch (error) {
1804
1772
  if (error instanceof BlockApiError && error.tag === "registrationPreparationChanged") commandRef.current = null;
1805
1773
  const { message, action } = registrationErrorPresentation(error);
@@ -1823,7 +1791,7 @@ function useEventRegistrationSubmitWithDeps(options, deps) {
1823
1791
  return pending;
1824
1792
  };
1825
1793
  }
1826
- async function completeRegistrationSubmission(options, deps, prepared, result, submitOptions) {
1794
+ async function completeRegistrationSubmission(options, deps, prepared, result) {
1827
1795
  if (result.demoBookingPass) storeDemoBookingPass(options.formData.email, result.demoBookingPass);
1828
1796
  switch (result.kind) {
1829
1797
  case "paymentStatusRequired":
@@ -1838,10 +1806,24 @@ async function completeRegistrationSubmission(options, deps, prepared, result, s
1838
1806
  outcome: { kind: "payment_status" }
1839
1807
  };
1840
1808
  case "checkoutRequired":
1841
- options.setPendingRegistrationReference(result.reference);
1842
1809
  savePendingCheckout(options, deps, prepared, result.submissionId, result.reference, result.mailingListSignupNote);
1843
- options.setStep("redirecting");
1844
- return redirectToCheckout(result.checkoutUrl, submitOptions);
1810
+ const subject = result.reference.scope === "occurrence" ? {
1811
+ scope: "occurrence",
1812
+ attendeeId: result.reference.attendeeId,
1813
+ submissionId: result.submissionId
1814
+ } : {
1815
+ scope: "wholeSeries",
1816
+ submissionId: result.submissionId
1817
+ };
1818
+ options.openPayment(result.checkout, subject, result.mailingListSignupNote);
1819
+ return {
1820
+ ok: true,
1821
+ outcome: {
1822
+ kind: "checkout_ready",
1823
+ checkout: result.checkout,
1824
+ subject
1825
+ }
1826
+ };
1845
1827
  case "waitlisted":
1846
1828
  options.setRegistrationResult({
1847
1829
  status: "waitlist",
@@ -1853,7 +1835,6 @@ async function completeRegistrationSubmission(options, deps, prepared, result, s
1853
1835
  break;
1854
1836
  case "bookingComplete":
1855
1837
  case "paymentDeferred":
1856
- options.setPendingRegistrationReference(result.reference);
1857
1838
  options.setRegistrationResult({
1858
1839
  status: "confirmed",
1859
1840
  message: result.summary.paymentMethod === "pass" ? result.summary.message : void 0,
@@ -1926,19 +1907,12 @@ function savePendingCheckout(options, deps, prepared, checkoutAttemptId, referen
1926
1907
  selectedEventId: options.registrationTarget?.selectedEventId ?? null,
1927
1908
  selectedAttendanceMode: options.selectedAttendanceMode,
1928
1909
  paymentSelection: prepared.selectionSnapshot,
1910
+ guests: options.guests,
1911
+ selectedSlidingAmount: options.selectedSlidingAmount,
1912
+ discountCode: options.discountCode,
1929
1913
  mailingListSignupNote
1930
1914
  });
1931
1915
  }
1932
- function redirectToCheckout(checkoutUrl, submitOptions) {
1933
- if (submitOptions?.redirect !== "manual") window.location.href = checkoutUrl;
1934
- return {
1935
- ok: true,
1936
- outcome: {
1937
- kind: "checkout_redirect",
1938
- checkoutUrl
1939
- }
1940
- };
1941
- }
1942
1916
  function registrationRequestIntentKey(request) {
1943
1917
  const { registrationCommandId: _command, altchaSolution: _proof, mailingListOptIn: _marketingConsent, checkout, ...intent } = request;
1944
1918
  return JSON.stringify({
@@ -1953,7 +1927,7 @@ function buildRegistrationRequest(options, prepared, method, passId, membershipI
1953
1927
  return {
1954
1928
  registrationCommandId: prepared.registrationCommandId,
1955
1929
  checkout: {
1956
- ...buildCheckoutReturnUrls("registration", EVENT_REGISTRATION_ANCHOR_ID, { [EVENT_REGISTRATION_CHECKOUT_ATTEMPT_PARAM]: prepared.registrationCommandId }),
1930
+ returnUrl: eventPaymentReturnUrl(prepared.registrationCommandId),
1957
1931
  discountCode: options.discountCode.trim() || void 0,
1958
1932
  collectionOptionKind: prepared.selectionSnapshot.method === "stripe" ? prepared.selectionSnapshot.collectionOptionKind : void 0
1959
1933
  },
@@ -2075,7 +2049,7 @@ function getRegistrationOccurrenceId(target) {
2075
2049
  return target?.subject.kind === "occurrence" ? target.subject.occurrenceId : "";
2076
2050
  }
2077
2051
  function shouldLoadRegistrationOptions(authState, step) {
2078
- return authState === "authenticated" || step === "select-tickets" || step === "enter-details" || step === "select-payment";
2052
+ return authState === "authenticated" || step === "select-tickets" || step === "fully-booked" || step === "enter-details" || step === "select-payment";
2079
2053
  }
2080
2054
  function getEligibilityFlowFlags(input) {
2081
2055
  const hasPass = (input.eligibility?.eligiblePasses.length ?? 0) > 0;
@@ -2174,132 +2148,544 @@ function useWaitlistClaimRestoration(input) {
2174
2148
  ]);
2175
2149
  }
2176
2150
  //#endregion
2177
- //#region ../blocks/src/system/runtime/nodes/event-registration/registrationCompletion.ts
2178
- function isSelectionSoldOut(availability) {
2179
- return availability?.kind === "unavailable" && availability.reason === "full";
2180
- }
2181
- function resolveRegistrationAvailabilityBlocker(input) {
2182
- if (input.hasWaitlistClaim) return null;
2183
- if (input.eligibilityError) return input.eligibilityError;
2184
- if (!input.eligibility) return "Registration is not available for this event.";
2185
- if (input.eligibility.registration.kind === "pending_payment") return null;
2186
- if (isSelectionSoldOut(input.eligibility.availability)) return null;
2187
- if (input.eligibility.availability.kind === "unavailable" && input.eligibility.availability.reason === "paymentUnavailable") return "Payment options are not available for this event.";
2188
- if (input.eligibility.availability.kind === "unavailable" || input.eligibility.registration.kind === "waitlisted") return "Registration is not available for this event.";
2189
- return null;
2151
+ //#region ../blocks/src/system/runtime/nodes/event-registration/useGuestRegistrationRecovery.ts
2152
+ /** Guest email is navigation permission only. Discard checks when the visitor
2153
+ * changes identity or selection before the response arrives. */
2154
+ function useGuestRegistrationRecovery(input) {
2155
+ const [checkingNextStep, setCheckingNextStep] = useState(false);
2156
+ const [recoveryMessage, setRecoveryMessage] = useState(null);
2157
+ const request = guestNextStepRequest(input);
2158
+ const requestKey = JSON.stringify([
2159
+ request,
2160
+ input.step,
2161
+ input.portalAuthState,
2162
+ input.admission
2163
+ ]);
2164
+ const currentRequest = useRef(requestKey);
2165
+ currentRequest.current = requestKey;
2166
+ const checkDetailsNextStep = () => checkGuestNextStep(input, request, setCheckingNextStep);
2167
+ const handleRecoveryEmail = () => recoverByEmail({
2168
+ ...input,
2169
+ checkingNextStep,
2170
+ check: checkDetailsNextStep,
2171
+ setRecoveryMessage,
2172
+ isCurrent: () => currentRequest.current === requestKey
2173
+ });
2174
+ return {
2175
+ checkingNextStep,
2176
+ recoveryMessage,
2177
+ checkDetailsNextStep,
2178
+ handleRecoveryEmail
2179
+ };
2190
2180
  }
2191
- function resolvePricingSelectionBlocker(input) {
2192
- if (input.selectionRequired && !input.hasValidSelection) return "Choose at least one available ticket to continue.";
2193
- return input.slidingScaleSelectionMessage;
2181
+ async function checkGuestNextStep(input, request, setCheckingNextStep) {
2182
+ const plan = resolveGuestNextStepCheck(input.admission, request);
2183
+ switch (plan.kind) {
2184
+ case "skip": return plan.outcome;
2185
+ case "blocked": return {
2186
+ kind: "unavailable",
2187
+ message: plan.message
2188
+ };
2189
+ case "check": break;
2190
+ }
2191
+ setCheckingNextStep(true);
2192
+ try {
2193
+ return await input.api.checkRegistrationNextStep(plan.request);
2194
+ } catch {
2195
+ return {
2196
+ kind: "unavailable",
2197
+ message: "We could not check availability. Please try again."
2198
+ };
2199
+ } finally {
2200
+ setCheckingNextStep(false);
2201
+ }
2202
+ }
2203
+ async function recoverByEmail(input) {
2204
+ if (input.checkingNextStep) return {
2205
+ ok: false,
2206
+ reason: "blocked",
2207
+ message: "Your booking is being checked."
2208
+ };
2209
+ const emailError = validateFormData(input.formData).email;
2210
+ input.setErrors(emailError ? { email: emailError } : {});
2211
+ input.setRecoveryMessage(null);
2212
+ if (emailError) return {
2213
+ ok: false,
2214
+ reason: "validation-failed",
2215
+ message: emailError
2216
+ };
2217
+ const next = await input.check();
2218
+ if (!input.isCurrent()) return {
2219
+ ok: false,
2220
+ reason: "blocked",
2221
+ message: "Your booking details changed. Please check again."
2222
+ };
2223
+ switch (next.kind) {
2224
+ case "continue":
2225
+ case "waitlist":
2226
+ input.onRecoveryChecked(next);
2227
+ return { ok: true };
2228
+ case "unavailable":
2229
+ input.setRecoveryMessage(next.message);
2230
+ return {
2231
+ ok: false,
2232
+ reason: "blocked",
2233
+ message: next.message
2234
+ };
2235
+ }
2236
+ }
2237
+ function guestNextStepRequest(input) {
2238
+ return input.registrationTarget ? {
2239
+ subject: toRegistrationRequestSubject(input.registrationTarget.subject, input.selectedAttendanceMode),
2240
+ selections: input.ticketSelections,
2241
+ selectedSlidingAmount: input.selectedSlidingAmount ?? void 0,
2242
+ email: input.formData.email,
2243
+ altchaSolution: input.altchaSolution
2244
+ } : null;
2194
2245
  }
2195
2246
  //#endregion
2196
- //#region ../blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.ts
2197
- function isCheckableRecovery(state) {
2198
- return state.kind === "ready" || state.kind === "processing" || state.kind === "temporarilyUnavailable";
2247
+ //#region ../blocks/src/system/runtime/nodes/event-registration/paymentResponse.ts
2248
+ /**
2249
+ * Verified response identity owns every follow-up action, including storage-free returns.
2250
+ * `requested` is the subject the client sent, if any: a subject-bound `notFound`
2251
+ * means the server lost that booking, which no amount of re-checking will fix.
2252
+ */
2253
+ function planPaymentResponse(response, intent, requested) {
2254
+ switch (response.kind) {
2255
+ case "checkout": return planCheckoutOutcome(response, intent);
2256
+ case "notFound": return requested ? assistance(requested) : { kind: "unresolved" };
2257
+ case "reconciliationRequired": return { kind: "unresolved" };
2258
+ default: return assertNever(response);
2259
+ }
2199
2260
  }
2200
- function assistanceRequired(recovery) {
2261
+ function assistance(subject) {
2201
2262
  return {
2202
- kind: "assistanceRequired",
2203
- subject: recovery.subject,
2204
- mailingListSignupNote: recovery.mailingListSignupNote
2263
+ kind: "recovery",
2264
+ state: {
2265
+ kind: "assistanceRequired",
2266
+ subject
2267
+ }
2205
2268
  };
2206
2269
  }
2207
- function applyStatusOutcome(outcome, recovery, setters, setRecovery) {
2208
- if (outcome.kind === "resolved") {
2209
- setters.setPendingRegistrationReference(outcome.reference);
2210
- setters.setRegistrationResult({
2211
- status: "confirmed",
2212
- mailingListSignupNote: recovery.mailingListSignupNote
2213
- });
2214
- setRecovery(null);
2215
- setters.setStep("success");
2216
- return;
2270
+ function planCheckoutOutcome({ outcome, subject }, intent) {
2271
+ switch (outcome.kind) {
2272
+ case "resolved": return { kind: "confirmed" };
2273
+ case "paymentAvailable": return intent === "resume" ? {
2274
+ kind: "payment",
2275
+ checkout: outcome.checkout,
2276
+ subject
2277
+ } : {
2278
+ kind: "recovery",
2279
+ state: {
2280
+ kind: "paymentAvailable",
2281
+ checkout: outcome.checkout,
2282
+ subject
2283
+ }
2284
+ };
2285
+ case "abandoned":
2286
+ case "paymentRetryAvailable": return intent === "abandon" ? { kind: "restartBooking" } : {
2287
+ kind: "recovery",
2288
+ state: {
2289
+ kind: "retryAvailable",
2290
+ subject
2291
+ }
2292
+ };
2293
+ case "paymentProcessing": return {
2294
+ kind: "recovery",
2295
+ state: {
2296
+ kind: "processing",
2297
+ subject
2298
+ }
2299
+ };
2300
+ case "paymentCheckUnavailable": return {
2301
+ kind: "recovery",
2302
+ state: {
2303
+ kind: "temporarilyUnavailable",
2304
+ subject
2305
+ }
2306
+ };
2307
+ case "reconciliationRequired":
2308
+ case "paymentAssistanceRequired": return assistance(subject);
2309
+ default: return assertNever(outcome);
2310
+ }
2311
+ }
2312
+ function paymentCheckEvidence(result) {
2313
+ switch (result.kind) {
2314
+ case "unreachable": return { kind: "inconclusive" };
2315
+ case "notFound": return { kind: "bindingLost" };
2316
+ case "reconciliationRequired": return { kind: "paymentUnderway" };
2317
+ case "checkout": return checkoutEvidence(result);
2318
+ default: return assertNever(result);
2217
2319
  }
2320
+ }
2321
+ function checkoutEvidence(response) {
2322
+ const { outcome } = response;
2218
2323
  switch (outcome.kind) {
2324
+ case "paymentCheckUnavailable": return { kind: "inconclusive" };
2325
+ case "paymentAvailable": return {
2326
+ kind: "payable",
2327
+ checkout: outcome.checkout,
2328
+ response
2329
+ };
2219
2330
  case "paymentProcessing":
2220
- setRecovery({
2221
- ...recovery,
2222
- kind: "processing"
2223
- });
2224
- return;
2225
- case "paymentAvailable":
2226
- setRecovery({
2227
- ...recovery,
2228
- kind: "paymentAvailable",
2229
- checkoutUrl: outcome.checkoutUrl
2230
- });
2231
- return;
2232
- case "paymentCheckUnavailable":
2233
- setRecovery({
2234
- ...recovery,
2235
- kind: "temporarilyUnavailable"
2236
- });
2237
- return;
2331
+ case "reconciliationRequired": return { kind: "paymentUnderway" };
2332
+ case "resolved":
2333
+ case "abandoned":
2238
2334
  case "paymentRetryAvailable":
2239
- setRecovery({
2240
- ...recovery,
2241
- kind: "retryAvailable"
2335
+ case "paymentAssistanceRequired": return {
2336
+ kind: "terminal",
2337
+ response
2338
+ };
2339
+ default: return assertNever(outcome);
2340
+ }
2341
+ }
2342
+ function planMountedPaymentRefresh(result, mounted) {
2343
+ const evidence = paymentCheckEvidence(result);
2344
+ switch (evidence.kind) {
2345
+ case "inconclusive": return { kind: "stay" };
2346
+ case "paymentUnderway": return { kind: "poll" };
2347
+ case "bindingLost": return {
2348
+ kind: "leave",
2349
+ transition: assistance(mounted.subject)
2350
+ };
2351
+ case "payable": return evidence.checkout.sessionId === mounted.sessionId ? { kind: "stay" } : {
2352
+ kind: "leave",
2353
+ transition: planCheckoutOutcome(evidence.response, "resume")
2354
+ };
2355
+ case "terminal": return {
2356
+ kind: "leave",
2357
+ transition: planCheckoutOutcome(evidence.response, "check")
2358
+ };
2359
+ default: return assertNever(evidence);
2360
+ }
2361
+ }
2362
+ function paymentResponseNeedsPolling(response) {
2363
+ if (response.kind !== "checkout") return response.kind === "reconciliationRequired";
2364
+ return paymentOutcomeNeedsPolling(response.outcome);
2365
+ }
2366
+ function paymentOutcomeNeedsPolling(outcome) {
2367
+ return outcome.kind === "paymentProcessing" || outcome.kind === "reconciliationRequired" || outcome.kind === "paymentCheckUnavailable";
2368
+ }
2369
+ //#endregion
2370
+ //#region ../blocks/src/system/runtime/nodes/event-registration/usePaymentStatusRecovery.ts
2371
+ function usePaymentRuntime() {
2372
+ const [state, setState] = useState({ kind: "idle" });
2373
+ const note = useRef(void 0);
2374
+ const operation = useRef(null);
2375
+ const generation = useRef(0);
2376
+ const appliedResult = useRef(null);
2377
+ const current = useRef(state);
2378
+ current.current = state;
2379
+ useEffect(() => () => {
2380
+ generation.current += 1;
2381
+ }, []);
2382
+ return {
2383
+ state,
2384
+ setState,
2385
+ note,
2386
+ operation,
2387
+ generation,
2388
+ appliedResult,
2389
+ current
2390
+ };
2391
+ }
2392
+ function usePaymentTransitions(options, runtime) {
2393
+ const { setState, note, generation, operation } = runtime;
2394
+ const openPayment = useCallback((checkout, subject, mailingListSignupNote) => {
2395
+ if (mailingListSignupNote) note.current = mailingListSignupNote;
2396
+ rememberEventPayment(subject.submissionId, checkout.sessionId);
2397
+ setState({
2398
+ kind: "payment",
2399
+ checkout,
2400
+ subject
2401
+ });
2402
+ options.setStep("payment");
2403
+ }, [
2404
+ options.setStep,
2405
+ setState,
2406
+ note
2407
+ ]);
2408
+ const verify = useCallback((request, resumePayment = true) => {
2409
+ if (operation.current) return;
2410
+ generation.current += 1;
2411
+ setState({
2412
+ kind: "verifying",
2413
+ request,
2414
+ resumePayment
2415
+ });
2416
+ options.setStep("verifying-payment");
2417
+ }, [
2418
+ options.setStep,
2419
+ operation,
2420
+ generation,
2421
+ setState
2422
+ ]);
2423
+ const restartFrom = useCallback((step) => {
2424
+ clearEventPayment(options.api.config.siteId);
2425
+ options.beginNewSubmission();
2426
+ setState({ kind: "idle" });
2427
+ options.setOptionsRefreshKey((value) => value + 1);
2428
+ options.setStep(step);
2429
+ }, [options, setState]);
2430
+ const resetBooking = useCallback(() => restartFrom("select-tickets"), [restartFrom]);
2431
+ const returnToPaymentOptions = useCallback(() => restartFrom(options.detailsInPage() ? "select-payment" : "select-tickets"), [restartFrom, options]);
2432
+ const applyTransition = useCallback((transition) => {
2433
+ switch (transition.kind) {
2434
+ case "confirmed":
2435
+ options.setRegistrationResult({
2436
+ status: "confirmed",
2437
+ mailingListSignupNote: note.current
2438
+ });
2439
+ clearEventPayment(options.api.config.siteId);
2440
+ setState({ kind: "idle" });
2441
+ options.setStep("success");
2442
+ return;
2443
+ case "payment": return openPayment(transition.checkout, transition.subject);
2444
+ case "restartBooking": return resetBooking();
2445
+ case "recovery":
2446
+ setState({
2447
+ kind: "recovery",
2448
+ state: {
2449
+ ...transition.state,
2450
+ mailingListSignupNote: note.current
2451
+ }
2452
+ });
2453
+ options.setStep("payment-status");
2454
+ return;
2455
+ case "unresolved": return options.setStep("verifying-timeout");
2456
+ default: return assertNever(transition);
2457
+ }
2458
+ }, [
2459
+ openPayment,
2460
+ resetBooking,
2461
+ options,
2462
+ setState,
2463
+ note
2464
+ ]);
2465
+ return {
2466
+ openPayment,
2467
+ verify,
2468
+ applyOutcome: useCallback((response, intent, requested) => applyTransition(planPaymentResponse(response, intent, requested)), [applyTransition]),
2469
+ applyTransition,
2470
+ resetBooking,
2471
+ returnToPaymentOptions
2472
+ };
2473
+ }
2474
+ /** A session-only return has no client subject; the server's envelope must supply one. */
2475
+ function requestedSubject(request) {
2476
+ return request.kind === "resolveSession" ? null : request.subject;
2477
+ }
2478
+ function usePaymentChecks(options, runtime, transitions) {
2479
+ const { state, appliedResult, current } = runtime;
2480
+ const request = state.kind === "verifying" ? state.request : null;
2481
+ const polling = usePolling(request ? {
2482
+ restartKey: JSON.stringify(request),
2483
+ pollFn: () => options.api.resolveEventCheckoutReturn(request),
2484
+ isComplete: (result) => !paymentResponseNeedsPolling(result),
2485
+ maxAttempts: 10,
2486
+ interval: 1e3
2487
+ } : null);
2488
+ useEffect(() => {
2489
+ if (polling.status === "completed" && polling.result && request && appliedResult.current !== polling.result) {
2490
+ appliedResult.current = polling.result;
2491
+ transitions.applyOutcome(polling.result, state.kind === "verifying" && state.resumePayment ? "resume" : "check", requestedSubject(request));
2492
+ } else if (polling.status === "timeout") options.setStep("verifying-timeout");
2493
+ }, [
2494
+ polling.status,
2495
+ polling.result,
2496
+ request,
2497
+ transitions.applyOutcome,
2498
+ options.setStep,
2499
+ appliedResult
2500
+ ]);
2501
+ const check = useCallback((resumePayment = false) => {
2502
+ appliedResult.current = null;
2503
+ const active = current.current;
2504
+ if (active.kind === "payment") transitions.verify({
2505
+ kind: "checkStatus",
2506
+ subject: active.subject
2507
+ }, resumePayment);
2508
+ else if (active.kind === "recovery") transitions.verify({
2509
+ kind: "checkStatus",
2510
+ subject: active.state.subject
2511
+ }, resumePayment);
2512
+ else if (active.kind === "verifying") {
2513
+ options.setStep("verifying-payment");
2514
+ polling.retry();
2515
+ }
2516
+ }, [
2517
+ transitions.verify,
2518
+ polling.retry,
2519
+ options.setStep,
2520
+ appliedResult,
2521
+ current
2522
+ ]);
2523
+ const checkStatus = useCallback(() => check(), [check]);
2524
+ return {
2525
+ check: checkStatus,
2526
+ retryPaymentLoad: useCallback(() => check(true), [check]),
2527
+ verification: {
2528
+ ...polling,
2529
+ retry: checkStatus,
2530
+ retryReturn: checkStatus
2531
+ }
2532
+ };
2533
+ }
2534
+ function usePaymentAbandonment(options, runtime, transitions) {
2535
+ const { operation, generation, current, setState } = runtime;
2536
+ return useCallback(() => {
2537
+ if (operation.current) return operation.current;
2538
+ const active = current.current;
2539
+ if (active.kind !== "payment") return Promise.resolve();
2540
+ const token = ++generation.current;
2541
+ setState({
2542
+ kind: "abandoning",
2543
+ subject: active.subject
2544
+ });
2545
+ const pending = options.api.resolveEventCheckoutReturn({
2546
+ kind: "abandon",
2547
+ subject: active.subject
2548
+ }).then((outcome) => {
2549
+ if (token !== generation.current) return;
2550
+ transitions.applyOutcome(outcome, "abandon", active.subject);
2551
+ }).catch(() => {
2552
+ if (token !== generation.current) return;
2553
+ setState({
2554
+ kind: "recovery",
2555
+ state: {
2556
+ kind: "temporarilyUnavailable",
2557
+ subject: active.subject,
2558
+ mailingListSignupNote: runtime.note.current
2559
+ }
2560
+ });
2561
+ options.setStep("payment-status");
2562
+ });
2563
+ operation.current = pending;
2564
+ pending.finally(() => {
2565
+ if (operation.current === pending) operation.current = null;
2566
+ });
2567
+ return pending;
2568
+ }, [
2569
+ options.api,
2570
+ transitions.applyOutcome,
2571
+ operation,
2572
+ generation,
2573
+ current,
2574
+ setState,
2575
+ options.setStep,
2576
+ runtime.note
2577
+ ]);
2578
+ }
2579
+ /**
2580
+ * Returning to the tab (often from a banking app mid-authentication) must not
2581
+ * unmount Stripe on inconclusive evidence; `planMountedPaymentRefresh` decides.
2582
+ */
2583
+ function usePaymentBackgroundCheck(options, runtime, transitions) {
2584
+ const { current, generation, operation } = runtime;
2585
+ const inFlight = useRef(false);
2586
+ return useCallback(() => {
2587
+ const active = current.current;
2588
+ if (active.kind !== "payment" || operation.current || inFlight.current) return;
2589
+ const token = generation.current;
2590
+ inFlight.current = true;
2591
+ const request = {
2592
+ kind: "checkStatus",
2593
+ subject: active.subject
2594
+ };
2595
+ const unreachable = { kind: "unreachable" };
2596
+ options.api.resolveEventCheckoutReturn(request).catch(() => unreachable).then((result) => {
2597
+ if (token !== generation.current || current.current !== active) return;
2598
+ const refresh = planMountedPaymentRefresh(result, {
2599
+ sessionId: active.checkout.sessionId,
2600
+ subject: active.subject
2242
2601
  });
2243
- return;
2244
- case "abandoned":
2245
- case "notFound":
2246
- case "reconciliationRequired":
2247
- case "paymentAssistanceRequired":
2248
- setRecovery(assistanceRequired(recovery));
2249
- return;
2250
- }
2602
+ switch (refresh.kind) {
2603
+ case "stay": return;
2604
+ case "poll": return transitions.verify(request, false);
2605
+ case "leave": return transitions.applyTransition(refresh.transition);
2606
+ default: return assertNever(refresh);
2607
+ }
2608
+ }).finally(() => {
2609
+ inFlight.current = false;
2610
+ });
2611
+ }, [
2612
+ options.api,
2613
+ transitions.verify,
2614
+ transitions.applyTransition,
2615
+ current,
2616
+ generation,
2617
+ operation
2618
+ ]);
2619
+ }
2620
+ function usePaymentRefresh(state, check) {
2621
+ useEffect(() => {
2622
+ if (state.kind !== "payment") return;
2623
+ const timeout = window.setTimeout(check, Math.max(0, Date.parse(state.checkout.expiresAt) - Date.now()));
2624
+ const visible = () => {
2625
+ if (document.visibilityState === "visible") check();
2626
+ };
2627
+ const restored = (event) => {
2628
+ if (event.persisted) check();
2629
+ };
2630
+ document.addEventListener("visibilitychange", visible);
2631
+ window.addEventListener("pageshow", restored);
2632
+ return () => {
2633
+ clearTimeout(timeout);
2634
+ document.removeEventListener("visibilitychange", visible);
2635
+ window.removeEventListener("pageshow", restored);
2636
+ };
2637
+ }, [state, check]);
2251
2638
  }
2252
2639
  function usePaymentStatusRecovery(options) {
2253
- const [recovery, setRecovery] = useState(null);
2254
- const checkRef = useRef(null);
2640
+ const runtime = usePaymentRuntime();
2641
+ const transitions = usePaymentTransitions(options, runtime);
2642
+ const checks = usePaymentChecks(options, runtime, transitions);
2643
+ const backToBooking = usePaymentAbandonment(options, runtime, transitions);
2644
+ const backgroundCheck = usePaymentBackgroundCheck(options, runtime, transitions);
2645
+ const { state, setState, generation, note } = runtime;
2646
+ usePaymentRefresh(state, backgroundCheck);
2647
+ const recovery = state.kind === "recovery" ? state.state : null;
2255
2648
  return {
2649
+ state,
2256
2650
  recovery,
2257
- showRecovery: useCallback((next) => setRecovery(next), []),
2258
- check: useCallback(() => {
2259
- if (!recovery || !isCheckableRecovery(recovery) || checkRef.current) return;
2260
- const pending = (async () => {
2261
- setRecovery({
2262
- ...recovery,
2263
- kind: "checking"
2264
- });
2265
- try {
2266
- applyStatusOutcome(await options.api.resolveEventCheckoutReturn({
2267
- kind: "checkStatus",
2268
- subject: recovery.subject
2269
- }), recovery, options, setRecovery);
2270
- } catch {
2271
- setRecovery({
2272
- ...recovery,
2273
- kind: "temporarilyUnavailable"
2274
- });
2275
- }
2276
- })();
2277
- checkRef.current = pending;
2278
- pending.finally(() => {
2279
- if (checkRef.current === pending) checkRef.current = null;
2280
- });
2281
- }, [options, recovery]),
2282
- continuePayment: useCallback(() => {
2283
- if (recovery?.kind === "paymentAvailable") window.location.assign(recovery.checkoutUrl);
2284
- }, [recovery]),
2285
- returnToPayment: useCallback(() => {
2286
- if (recovery?.kind !== "retryAvailable") return;
2287
- options.beginNewSubmission();
2288
- setRecovery(null);
2289
- options.setOptionsRefreshKey((value) => value + 1);
2290
- options.setStep("select-payment");
2291
- }, [options, recovery]),
2292
- returnToBooking: useCallback(() => {
2293
- if (recovery?.kind !== "assistanceRequired") return;
2294
- options.beginNewSubmission();
2295
- setRecovery(null);
2296
- options.setOptionsRefreshKey((value) => value + 1);
2297
- options.setStep("select-tickets");
2298
- }, [options, recovery])
2651
+ showRecovery: useCallback((next) => {
2652
+ generation.current += 1;
2653
+ note.current = next?.mailingListSignupNote;
2654
+ setState(next ? {
2655
+ kind: "recovery",
2656
+ state: next
2657
+ } : { kind: "idle" });
2658
+ }, [
2659
+ generation,
2660
+ note,
2661
+ setState
2662
+ ]),
2663
+ restoreMailingListNote: (value) => {
2664
+ note.current = value;
2665
+ },
2666
+ openPayment: transitions.openPayment,
2667
+ verify: transitions.verify,
2668
+ ...checks,
2669
+ backToBooking,
2670
+ continuePayment: () => {
2671
+ if (recovery?.kind === "paymentAvailable") transitions.openPayment(recovery.checkout, recovery.subject);
2672
+ },
2673
+ returnToPayment: () => {
2674
+ if (recovery?.kind === "retryAvailable") transitions.returnToPaymentOptions();
2675
+ },
2676
+ returnToBooking: () => {
2677
+ if (recovery?.kind === "retryAvailable") transitions.resetBooking();
2678
+ }
2299
2679
  };
2300
2680
  }
2301
2681
  //#endregion
2302
2682
  //#region ../blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.ts
2683
+ function resolveAvailableAttendanceMode(event, selected) {
2684
+ if (!event) return null;
2685
+ const mode = event.deliveryMode ?? "in_person";
2686
+ if (mode === "online" || mode === "in_person") return mode;
2687
+ return selected && event.attendanceModes?.some((item) => item.mode === selected) ? selected : null;
2688
+ }
2303
2689
  function usesTieredPricing(event) {
2304
2690
  return event !== null && event !== void 0 ? publicEventPricingStrategy(event.pricing) === "tiered" : false;
2305
2691
  }
@@ -2357,19 +2743,24 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2357
2743
  const [errorAction, setErrorAction] = useState("retry");
2358
2744
  const [optionsRefreshKey, setOptionsRefreshKey] = useState(0);
2359
2745
  const [altchaSolution, setAltchaSolution] = useState(null);
2360
- const [checkingNextStep, setCheckingNextStep] = useState(false);
2361
2746
  const { authState: portalAuthState, profile: portalProfile } = deps.usePortalAuth();
2362
2747
  const waitlistClaim = deps.useWaitlistClaim();
2363
2748
  const [showMagicLinkModal, setShowMagicLinkModal] = useState(false);
2364
- const [pendingRegistrationReference, setPendingRegistrationReference] = useState(null);
2365
2749
  const [paymentStepState, setPaymentStepState] = useState({
2366
2750
  status: "loading",
2367
2751
  canContinue: false,
2368
2752
  buttonText: "Continue",
2369
2753
  showLoginPrompt: false
2370
2754
  });
2755
+ const selectionLocked = [
2756
+ "payment",
2757
+ "submitting",
2758
+ "verifying-payment",
2759
+ "verifying-timeout",
2760
+ "payment-status"
2761
+ ].includes(step);
2371
2762
  useEffect(() => {
2372
- if (portalAuthState !== "authenticated" || !portalProfile) return;
2763
+ if (selectionLocked || portalAuthState !== "authenticated" || !portalProfile) return;
2373
2764
  setFormData((prev) => ({
2374
2765
  ...prev,
2375
2766
  email: portalProfile.email,
@@ -2391,16 +2782,17 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2391
2782
  step: nextStep
2392
2783
  });
2393
2784
  }, []);
2394
- const { recovery: paymentStatusRecovery, showRecovery: setPaymentStatusRecovery, check: handleCheckPaymentStatus, continuePayment: handleContinueExistingPayment, returnToPayment: handleReturnToPayment, returnToBooking: handleReturnToBooking } = usePaymentStatusRecovery({
2785
+ const paymentController = usePaymentStatusRecovery({
2395
2786
  api,
2396
2787
  beginNewSubmission: () => {
2397
2788
  commandRef.current = null;
2398
2789
  },
2399
- setPendingRegistrationReference,
2400
2790
  setRegistrationResult,
2401
2791
  setOptionsRefreshKey,
2402
- setStep
2792
+ setStep,
2793
+ detailsInPage: () => Object.keys(validateFormData(formData)).length === 0
2403
2794
  });
2795
+ const { recovery: paymentStatusRecovery, showRecovery: setPaymentStatusRecovery, check: handleCheckPaymentStatus, continuePayment: handleContinueExistingPayment, returnToPayment: handleReturnToPayment, returnToBooking: handleReturnToBooking } = paymentController;
2404
2796
  const usesTicketTypeSelections = usesTieredPricing(currentEvent);
2405
2797
  const eventTicketTypesForSelection = useMemo(() => getVisibleTicketTypes({
2406
2798
  event: currentEvent,
@@ -2502,26 +2894,14 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2502
2894
  });
2503
2895
  }, [portalAuthState]);
2504
2896
  useEffect(() => {
2505
- const event = selectedEvent ?? preSelectedEvent;
2506
- if (!event) {
2507
- if (selectedAttendanceMode !== null) setSelectedAttendanceMode(null);
2508
- return;
2509
- }
2510
- const deliveryMode = event.deliveryMode ?? "in_person";
2511
- if (deliveryMode === "online") {
2512
- if (selectedAttendanceMode !== "online") setSelectedAttendanceMode("online");
2513
- return;
2514
- }
2515
- if (deliveryMode === "in_person") {
2516
- if (selectedAttendanceMode !== "in_person") setSelectedAttendanceMode("in_person");
2517
- return;
2518
- }
2519
- if (selectedAttendanceMode && (event.attendanceModes ?? []).some((mode) => mode.mode === selectedAttendanceMode)) return;
2520
- setSelectedAttendanceMode(null);
2897
+ if (selectionLocked) return;
2898
+ const next = resolveAvailableAttendanceMode(selectedEvent ?? preSelectedEvent, selectedAttendanceMode);
2899
+ if (next !== selectedAttendanceMode) setSelectedAttendanceMode(next);
2521
2900
  }, [
2522
2901
  preSelectedEvent,
2523
2902
  selectedAttendanceMode,
2524
- selectedEvent
2903
+ selectedEvent,
2904
+ selectionLocked
2525
2905
  ]);
2526
2906
  useEffect(() => {
2527
2907
  dispatchFlow({
@@ -2529,25 +2909,26 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2529
2909
  eligibility
2530
2910
  });
2531
2911
  }, [eligibility]);
2532
- const verification = deps.useEventRegistrationStripeReturn({
2533
- api,
2534
- events: registrationEvents,
2535
- registrationSubject: registrationTarget?.subject ?? null,
2536
- step,
2537
- pendingRegistrationReference,
2538
- setFormData,
2539
- setTicketsCount,
2540
- setSelectedEvent,
2541
- setSelectedAttendanceMode,
2542
- setTicketSelections: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
2543
- setPaymentSelection: (selection) => dispatchFlow({
2544
- type: "set-manual-payment-selection",
2545
- selection
2546
- }),
2547
- setPendingRegistrationReference,
2548
- setStep,
2549
- setRegistrationResult: (result) => setRegistrationResult(result),
2550
- setErrorMessage
2912
+ const verification = paymentController.verification;
2913
+ deps.useEventRegistrationStripeReturn({
2914
+ siteId: api.config.siteId,
2915
+ verify: paymentController.verify,
2916
+ restore: (pending) => {
2917
+ paymentController.restoreMailingListNote(pending.mailingListSignupNote);
2918
+ setFormData(pending.formData);
2919
+ setSelectedTicketLines(pending.ticketSelections);
2920
+ setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
2921
+ setGuests(pending.guests);
2922
+ discountUi.setDiscountCode(pending.discountCode);
2923
+ setSelectedSlidingAmountInput(pending.selectedSlidingAmount === null ? "" : formatCentsAsCurrencyInput(pending.selectedSlidingAmount));
2924
+ setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
2925
+ const event = registrationEvents.find((item) => item.selectionId === pending.selectedEventId);
2926
+ if (event) setSelectedEvent(event);
2927
+ dispatchFlow({
2928
+ type: "set-manual-payment-selection",
2929
+ selection: pending.paymentSelection
2930
+ });
2931
+ }
2551
2932
  });
2552
2933
  const requireGuestDetails = useMemo(() => {
2553
2934
  return (selectedEvent ?? preSelectedEvent)?.requireGuestDetails === true;
@@ -2591,6 +2972,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2591
2972
  visibleOptions: visibleTicketTypes
2592
2973
  });
2593
2974
  useEffect(() => {
2975
+ if (selectionLocked) return;
2594
2976
  if (!usesTicketTypeSelections) {
2595
2977
  if (selectedTicketLines.length > 0) setSelectedTicketLines([]);
2596
2978
  return;
@@ -2613,6 +2995,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2613
2995
  usesTicketTypeSelections
2614
2996
  ]);
2615
2997
  useEffect(() => {
2998
+ if (selectionLocked) return;
2616
2999
  if (!requireGuestDetails) {
2617
3000
  if (guests.length > 0) setGuests([]);
2618
3001
  return;
@@ -2622,19 +3005,16 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2622
3005
  if (guests.length > 0) setGuests([]);
2623
3006
  return;
2624
3007
  }
2625
- if (guests.length !== guestCount) {
2626
- const newGuests = [];
2627
- for (let i = 0; i < guestCount; i++) newGuests.push(guests[i] ?? {
2628
- name: "",
2629
- email: "",
2630
- phone: ""
2631
- });
2632
- setGuests(newGuests);
2633
- }
3008
+ if (guests.length !== guestCount) setGuests(Array.from({ length: guestCount }, (_, index) => guests[index] ?? {
3009
+ name: "",
3010
+ email: "",
3011
+ phone: ""
3012
+ }));
2634
3013
  }, [
2635
3014
  ticketsCount,
2636
3015
  requireGuestDetails,
2637
- guests
3016
+ guests,
3017
+ selectionLocked
2638
3018
  ]);
2639
3019
  const validateForm = () => {
2640
3020
  const newErrors = validateFormData(formData, Boolean(currentEvent?.consent));
@@ -2663,49 +3043,55 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2663
3043
  selectedEventSlidingScale,
2664
3044
  selectedSlidingAmount
2665
3045
  ]);
2666
- const checkDetailsNextStep = async () => {
2667
- if (portalAuthState === "authenticated" || waitlistClaim.claim || eligibility?.availability.kind === "available" || !registrationTarget) return { kind: canRecoverToWaitlist(eligibility) ? "waitlist" : "continue" };
2668
- setCheckingNextStep(true);
2669
- try {
2670
- return await api.checkRegistrationNextStep({
2671
- subject: toRegistrationRequestSubject(registrationTarget.subject, selectedAttendanceMode),
2672
- selections: ticketSelections,
2673
- selectedSlidingAmount: selectedSlidingAmount ?? void 0,
2674
- email: formData.email,
2675
- altchaSolution
2676
- });
2677
- } catch {
2678
- return {
2679
- kind: "unavailable",
2680
- message: "We could not check availability. Please try again."
2681
- };
2682
- } finally {
2683
- setCheckingNextStep(false);
2684
- }
2685
- };
3046
+ const { admission, ticketContinuation, handleTicketsComplete, onRecoveryChecked } = useRegistrationAdmission({
3047
+ eligibility,
3048
+ eligibilityError,
3049
+ eligibilityLoading,
3050
+ hasWaitlistClaim: waitlistClaim.claim !== null,
3051
+ isAuthenticated: portalAuthState === "authenticated",
3052
+ existingRegistrationLoading: existingRegistrationResult.isLoading,
3053
+ ticketSelectionRequired: ticketTypeSelectionRequired,
3054
+ hasValidTicketSelection,
3055
+ dispatchFlow
3056
+ });
3057
+ const { checkingNextStep, recoveryMessage, checkDetailsNextStep, handleRecoveryEmail } = useGuestRegistrationRecovery({
3058
+ api,
3059
+ formData,
3060
+ onRecoveryChecked,
3061
+ setErrors,
3062
+ admission,
3063
+ step,
3064
+ portalAuthState,
3065
+ registrationTarget,
3066
+ selectedAttendanceMode,
3067
+ ticketSelections,
3068
+ selectedSlidingAmount,
3069
+ altchaSolution
3070
+ });
2686
3071
  const handleDetailsComplete = async () => {
2687
- const blockDetailsCompletion = (message) => {
2688
- setErrorAction("places");
2689
- setErrorMessage(message);
2690
- setStep("error");
3072
+ const blockDetailsCompletion = (blocker) => {
3073
+ if (blocker.kind === "unavailable") {
3074
+ setErrorAction("places");
3075
+ setErrorMessage(blocker.message);
3076
+ setStep("error");
3077
+ }
2691
3078
  return {
2692
3079
  ok: false,
2693
3080
  reason: "blocked",
2694
- message
3081
+ message: blocker.message
2695
3082
  };
2696
3083
  };
2697
- const availabilityBlocker = resolveRegistrationAvailabilityBlocker({
2698
- hasWaitlistClaim: waitlistClaim.claim !== null,
2699
- eligibility,
2700
- eligibilityError
2701
- });
3084
+ const availabilityBlocker = resolveRegistrationAvailabilityBlocker(admission);
2702
3085
  if (availabilityBlocker) return blockDetailsCompletion(availabilityBlocker);
2703
3086
  const pricingSelectionBlocker = resolvePricingSelectionBlocker({
2704
3087
  selectionRequired: ticketTypeSelectionRequired,
2705
3088
  hasValidSelection: hasValidTicketSelection,
2706
3089
  slidingScaleSelectionMessage
2707
3090
  });
2708
- if (pricingSelectionBlocker) return blockDetailsCompletion(pricingSelectionBlocker);
3091
+ if (pricingSelectionBlocker) return blockDetailsCompletion({
3092
+ kind: "unavailable",
3093
+ message: pricingSelectionBlocker
3094
+ });
2709
3095
  if (!validateForm()) {
2710
3096
  setGuestSectionShake(true);
2711
3097
  return {
@@ -2715,7 +3101,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2715
3101
  };
2716
3102
  }
2717
3103
  const next = await checkDetailsNextStep();
2718
- if (next.kind === "unavailable") return blockDetailsCompletion(next.message);
3104
+ if (next.kind === "unavailable") return blockDetailsCompletion({
3105
+ kind: "unavailable",
3106
+ message: next.message
3107
+ });
2719
3108
  const canJoinWaitlist = next.kind === "waitlist";
2720
3109
  if (!canJoinWaitlist && requiresPaymentStep({
2721
3110
  eventRequiresPayment,
@@ -2732,7 +3121,10 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2732
3121
  eventRequiresPayment,
2733
3122
  canJoinWaitlist
2734
3123
  });
2735
- if (!submitPlan.ok) return blockDetailsCompletion(submitPlan.message);
3124
+ if (!submitPlan.ok) return blockDetailsCompletion({
3125
+ kind: "unavailable",
3126
+ message: submitPlan.message
3127
+ });
2736
3128
  const submitResult = await handleSubmit(submitPlan.args.method, submitPlan.args.passId, submitPlan.args.membershipId, submitPlan.args.selection);
2737
3129
  if (!submitResult.ok) return {
2738
3130
  ok: false,
@@ -2780,6 +3172,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2780
3172
  const selectionMethod = paymentSelection.method;
2781
3173
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
2782
3174
  const handleSubmit = deps.useEventRegistrationSubmit({
3175
+ openPayment: paymentController.openPayment,
2783
3176
  commandRef,
2784
3177
  setErrorAction,
2785
3178
  api,
@@ -2795,7 +3188,6 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2795
3188
  discountCode: discountUi.discountCode,
2796
3189
  claimToken: waitlistClaim.token,
2797
3190
  waitlistEntryId: waitlistClaim.entryId,
2798
- setPendingRegistrationReference,
2799
3191
  setStep,
2800
3192
  setRegistrationResult: (result) => setRegistrationResult(result),
2801
3193
  setErrorMessage,
@@ -2817,7 +3209,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2817
3209
  });
2818
3210
  };
2819
3211
  const handleRetry = () => recoverRegistrationError(resolveRegistrationRecoveryAction(step, errorAction), {
2820
- canJoinWaitlist: canRecoverToWaitlist(eligibility),
3212
+ canJoinWaitlist: admission.kind === "waitlist",
2821
3213
  refreshOptions: () => setOptionsRefreshKey((value) => value + 1),
2822
3214
  clearError: () => setErrorMessage(""),
2823
3215
  setStep,
@@ -2828,8 +3220,7 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2828
3220
  setSelectedTicketLines,
2829
3221
  setSelectedSlidingAmountInput,
2830
3222
  setSelectedAttendanceMode,
2831
- setErrorMessage,
2832
- setPendingRegistrationReference
3223
+ setErrorMessage
2833
3224
  })
2834
3225
  });
2835
3226
  const handlePaymentStepStateChange = useCallback((state) => {
@@ -2860,21 +3251,35 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2860
3251
  preSelectedEvent,
2861
3252
  hasPreSelectedContext,
2862
3253
  step,
2863
- setStep,
3254
+ setStep: (next) => {
3255
+ if (!selectionLocked) setStep(next);
3256
+ else if (step === "payment") paymentController.backToBooking();
3257
+ },
3258
+ admission,
3259
+ ticketContinuation,
3260
+ handleTicketsComplete,
2864
3261
  selectedEvent,
2865
- setSelectedEvent,
3262
+ setSelectedEvent: (value) => {
3263
+ if (!selectionLocked) setSelectedEvent(value);
3264
+ },
2866
3265
  ticketsCount,
2867
- setTicketsCount,
3266
+ setTicketsCount: (value) => {
3267
+ if (!selectionLocked) setTicketsCount(value);
3268
+ },
2868
3269
  selectedAttendanceMode,
2869
- setSelectedAttendanceMode,
3270
+ setSelectedAttendanceMode: (value) => {
3271
+ if (!selectionLocked) setSelectedAttendanceMode(value);
3272
+ },
2870
3273
  ticketSelections,
2871
3274
  selectedTicketLines: normalizedSelectedTicketLines,
2872
3275
  setSelectedTicketLines: (lines) => {
2873
- if (waitlistClaim.claim) return;
3276
+ if (selectionLocked || waitlistClaim.claim) return;
2874
3277
  setSelectedTicketLines(normalizeTicketSelectionLines(lines));
2875
3278
  },
2876
3279
  selectedSlidingAmountInput,
2877
- setSelectedSlidingAmountInput,
3280
+ setSelectedSlidingAmountInput: (value) => {
3281
+ if (!selectionLocked) setSelectedSlidingAmountInput(value);
3282
+ },
2878
3283
  selectedSlidingAmount,
2879
3284
  selectedEventSlidingScale,
2880
3285
  slidingScaleSelectionMessage,
@@ -2887,10 +3292,14 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2887
3292
  eventRequiresPayment,
2888
3293
  formData,
2889
3294
  mailingListSignup,
2890
- setFormData,
3295
+ setFormData: (value) => {
3296
+ if (!selectionLocked) setFormData(value);
3297
+ },
2891
3298
  errors,
2892
3299
  guests,
2893
- setGuests,
3300
+ setGuests: (value) => {
3301
+ if (!selectionLocked) setGuests(value);
3302
+ },
2894
3303
  guestErrors,
2895
3304
  guestSectionShake,
2896
3305
  handleGuestShakeEnd,
@@ -2907,7 +3316,9 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2907
3316
  handleLogout,
2908
3317
  paymentStepState,
2909
3318
  handlePaymentStepStateChange,
2910
- handlePaymentSelectionChange,
3319
+ handlePaymentSelectionChange: (value) => {
3320
+ if (!selectionLocked) handlePaymentSelectionChange(value);
3321
+ },
2911
3322
  paymentSelection,
2912
3323
  isMembershipFastTrack,
2913
3324
  waitlistClaim,
@@ -2923,7 +3334,9 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2923
3334
  hasMembershipEligibility,
2924
3335
  requireGuestDetails,
2925
3336
  discountCode: discountUi.discountCode,
2926
- setDiscountCode: discountUi.setDiscountCode,
3337
+ setDiscountCode: (value) => {
3338
+ if (!selectionLocked) discountUi.setDiscountCode(value);
3339
+ },
2927
3340
  discountUiOpen: discountUi.discountUiOpen,
2928
3341
  setDiscountUiOpen: discountUi.setDiscountUiOpen,
2929
3342
  discountsAvailable: discountUi.discountsAvailable,
@@ -2933,49 +3346,31 @@ function useEventRegistrationWizardWithDeps(options, deps) {
2933
3346
  discountContext,
2934
3347
  shouldShowDiscountToggle,
2935
3348
  verification,
3349
+ paymentController,
2936
3350
  paymentStatusRecovery,
2937
3351
  handleCheckPaymentStatus,
2938
3352
  handleContinueExistingPayment,
2939
3353
  handleReturnToPayment,
2940
3354
  handleReturnToBooking,
3355
+ recoveryMessage,
3356
+ checkingNextStep,
3357
+ handleRecoveryEmail,
2941
3358
  handleDetailsComplete,
2942
3359
  handlePaymentSelect,
2943
3360
  handleUseCardInsteadOfPass,
2944
3361
  handleBookForSomeoneElse,
2945
- handleSubmit,
2946
- handleEventSelect,
3362
+ handleSubmit: (...args) => selectionLocked ? Promise.resolve({
3363
+ ok: false,
3364
+ reason: "blocked",
3365
+ message: "This registration step cannot continue."
3366
+ }) : handleSubmit(...args),
3367
+ handleEventSelect: (value) => {
3368
+ if (!selectionLocked) handleEventSelect(value);
3369
+ },
2947
3370
  handleRetry
2948
3371
  };
2949
3372
  }
2950
3373
  //#endregion
2951
- //#region ../blocks/src/system/runtime/nodes/event-registration/useEventCheckoutHandoffRestore.ts
2952
- function eventRegistrationReloadTarget(location) {
2953
- return `${location.pathname}${location.search}#${EVENT_REGISTRATION_ANCHOR_ID}`;
2954
- }
2955
- function reloadAtEventRegistration() {
2956
- const target = eventRegistrationReloadTarget(window.location);
2957
- window.history.replaceState(window.history.state, "", target);
2958
- window.location.reload();
2959
- }
2960
- const browserCheckoutHandoffRestoreDeps = {
2961
- addPageShowListener: (listener) => window.addEventListener("pageshow", listener),
2962
- removePageShowListener: (listener) => window.removeEventListener("pageshow", listener),
2963
- reloadAtRegistration: reloadAtEventRegistration
2964
- };
2965
- function useEventCheckoutHandoffRestore(active) {
2966
- useEventCheckoutHandoffRestoreWithDeps(active, browserCheckoutHandoffRestoreDeps);
2967
- }
2968
- function useEventCheckoutHandoffRestoreWithDeps(active, deps) {
2969
- useEffect(() => {
2970
- if (!active) return;
2971
- const handlePageShow = (event) => {
2972
- if (event.persisted) deps.reloadAtRegistration();
2973
- };
2974
- deps.addPageShowListener(handlePageShow);
2975
- return () => deps.removePageShowListener(handlePageShow);
2976
- }, [active, deps]);
2977
- }
2978
- //#endregion
2979
3374
  //#region ../blocks/src/system/runtime/nodes/event-registration/EventRegistrationBookingLayer.tsx
2980
3375
  function BookingEntry(props) {
2981
3376
  const base = props.variant === "teaser" ? "er-booking-teaser" : "er-booking-bar";
@@ -3577,49 +3972,23 @@ function EventRegistrationSummaryBar(props) {
3577
3972
  */
3578
3973
  function resolveDetailsButtonText(input) {
3579
3974
  const { wizard } = input;
3580
- const availability = wizard.eligibility?.availability;
3581
- const full = availability?.kind === "waitlist" || isSelectionSoldOut(availability);
3582
- if (wizard.portalAuthState !== "authenticated" && full) return "Continue";
3583
- if (canRecoverToWaitlist(wizard.eligibility)) return "Join waitlist";
3584
- if (!input.hasWaitlistClaim && full && wizard.eligibility?.registration.kind !== "pending_payment") return "Sold out";
3975
+ const admission = wizard.admission;
3976
+ switch (admission.kind) {
3977
+ case "checking": return "Checking availability...";
3978
+ case "closed": return admission.reason === "alreadyWaitlisted" ? "Already on waitlist" : "Registration unavailable";
3979
+ case "fullyBooked": return admission.recovery === "byEmail" ? "Continue" : "Sold out";
3980
+ case "waitlist": return admission.recovery === "byEmail" ? "Continue" : "Join waitlist";
3981
+ case "open":
3982
+ case "resume": break;
3983
+ }
3585
3984
  if (wizard.paymentSelection.method === "pass") return "Book using class pass";
3586
3985
  if (wizard.paymentSelection.method === "membership") return input.fallbackText;
3587
3986
  return wizard.eventRequiresPayment ? "Continue to payment" : input.fallbackText;
3588
3987
  }
3589
- function resolveExistingRegistrationLabel(wizard) {
3590
- return wizard.registrationState.kind === "waitlisted" ? "Already on waitlist" : "Already registered";
3591
- }
3592
- function isExistingRegistrationBlocking$1(wizard) {
3593
- return hasBlockingExistingRegistration({
3594
- isAuthenticated: wizard.portalAuthState === "authenticated",
3595
- registration: wizard.registrationState,
3596
- hasWaitlistClaim: wizard.waitlistClaim.claim !== null
3597
- });
3598
- }
3599
- function resolveTicketSelectionContinueLabel(input) {
3600
- if (input.isCheckingExistingRegistration) return "Checking registration...";
3601
- if (input.ticketTypeSelectionBlocked) return "Choose a ticket";
3602
- if (input.registrationOptionsPending) return "Checking availability...";
3603
- if (input.registrationOptionsFailed) return "Availability unavailable";
3604
- if (input.hasExistingRegistration) return resolveExistingRegistrationLabel(input.wizard);
3605
- return "Continue";
3606
- }
3607
3988
  function SelectTicketsActions(props) {
3608
3989
  const { wizard } = props;
3609
- const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidTicketSelection;
3610
- const registrationOptionsPending = !ticketTypeSelectionBlocked && ["idle", "loading"].includes(wizard.registrationOptionsState.tag);
3611
- const registrationOptionsFailed = wizard.registrationOptionsState.tag === "failed";
3612
- const isCheckingExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistrationLoading;
3613
- const hasExistingRegistration = isExistingRegistrationBlocking$1(wizard);
3614
- const isContinueDisabled = registrationOptionsPending || registrationOptionsFailed || isCheckingExistingRegistration || hasExistingRegistration || ticketTypeSelectionBlocked;
3615
- const continueLabel = resolveTicketSelectionContinueLabel({
3616
- wizard,
3617
- registrationOptionsPending,
3618
- registrationOptionsFailed,
3619
- isCheckingExistingRegistration,
3620
- hasExistingRegistration,
3621
- ticketTypeSelectionBlocked
3622
- });
3990
+ const { label: continueLabel, pending: registrationOptionsPending } = wizard.ticketContinuation;
3991
+ const isContinueDisabled = wizard.ticketContinuation.kind === "blocked";
3623
3992
  return /* @__PURE__ */ jsxs("div", {
3624
3993
  className: "rb-wizard-actions er-actions",
3625
3994
  children: [!wizard.hasPreSelectedContext && /* @__PURE__ */ jsx("button", {
@@ -3629,7 +3998,7 @@ function SelectTicketsActions(props) {
3629
3998
  children: "← Back"
3630
3999
  }), /* @__PURE__ */ jsx(ThemedButton, {
3631
4000
  type: "button",
3632
- onClick: () => wizard.setStep("enter-details"),
4001
+ onClick: wizard.handleTicketsComplete,
3633
4002
  disabled: isContinueDisabled,
3634
4003
  variant: props.buttonVariant,
3635
4004
  size: "md",
@@ -3645,18 +4014,11 @@ function SelectTicketsActions(props) {
3645
4014
  }
3646
4015
  function EnterDetailsActions(props) {
3647
4016
  const { wizard } = props;
3648
- const hasWaitlistClaim = wizard.waitlistClaim.claim !== null;
3649
4017
  const isCheckingEligibility = wizard.eligibilityLoading;
3650
- const eligibilityUnavailable = Boolean(resolveRegistrationAvailabilityBlocker({
3651
- hasWaitlistClaim,
3652
- eligibility: wizard.eligibility,
3653
- eligibilityError: wizard.eligibilityError
3654
- }));
3655
- const isSoldOut = !hasWaitlistClaim && wizard.portalAuthState === "authenticated" && wizard.eligibility?.registration.kind !== "pending_payment" && isSelectionSoldOut(wizard.eligibility?.availability);
3656
- const isContinueDisabled = isCheckingEligibility || eligibilityUnavailable || isSoldOut;
4018
+ const eligibilityUnavailable = Boolean(resolveRegistrationAvailabilityBlocker(wizard.admission));
4019
+ const isContinueDisabled = isCheckingEligibility || eligibilityUnavailable;
3657
4020
  const continueLabel = isCheckingEligibility ? "Checking availability..." : wizard.eligibilityError ? "Availability unavailable" : eligibilityUnavailable ? "Registration unavailable" : resolveDetailsButtonText({
3658
4021
  wizard,
3659
- hasWaitlistClaim,
3660
4022
  fallbackText: props.buttonText
3661
4023
  });
3662
4024
  return /* @__PURE__ */ jsxs("div", {
@@ -3699,10 +4061,11 @@ function SelectPaymentActions(props) {
3699
4061
  function EventRegistrationWizardActions(props) {
3700
4062
  const { wizard } = props;
3701
4063
  switch (wizard.step) {
4064
+ case "fully-booked":
3702
4065
  case "error":
3703
4066
  case "success":
3704
4067
  case "submitting":
3705
- case "redirecting":
4068
+ case "payment":
3706
4069
  case "verifying-payment":
3707
4070
  case "verifying-timeout":
3708
4071
  case "payment-status":
@@ -3715,6 +4078,118 @@ function EventRegistrationWizardActions(props) {
3715
4078
  }
3716
4079
  }
3717
4080
  //#endregion
4081
+ //#region ../blocks/src/system/runtime/nodes/event-registration/EventRegistrationPayment.tsx
4082
+ const Payment = lazy(() => import("./EventEmbeddedCheckoutStep-DL6zFAkA.mjs"));
4083
+ /** The one Stripe mount, shared by the standard wizard and headless arrangements. */
4084
+ function EventPaymentMount({ state, onComplete, onRetry, onBack }) {
4085
+ if (state.kind === "abandoning") return /* @__PURE__ */ jsx("p", {
4086
+ role: "status",
4087
+ children: "Returning to booking…"
4088
+ });
4089
+ if (state.kind !== "payment") return null;
4090
+ return /* @__PURE__ */ jsx(Suspense, {
4091
+ fallback: /* @__PURE__ */ jsx("p", {
4092
+ role: "status",
4093
+ children: "Loading secure payment…"
4094
+ }),
4095
+ children: /* @__PURE__ */ jsx(Payment, {
4096
+ checkout: state.checkout,
4097
+ onComplete,
4098
+ onRetry,
4099
+ onBack: () => void onBack()
4100
+ })
4101
+ });
4102
+ }
4103
+ //#endregion
4104
+ //#region ../blocks/src/system/runtime/nodes/event-registration/FullyBookedStep.tsx
4105
+ function RecoveryEmailField({ wizard }) {
4106
+ const id = useId();
4107
+ const errorId = `${id}-error`;
4108
+ return /* @__PURE__ */ jsxs("div", {
4109
+ className: "er-field",
4110
+ children: [
4111
+ /* @__PURE__ */ jsx("label", {
4112
+ className: "er-label",
4113
+ htmlFor: id,
4114
+ children: "Email"
4115
+ }),
4116
+ /* @__PURE__ */ jsx("input", {
4117
+ id,
4118
+ type: "email",
4119
+ autoComplete: "email",
4120
+ className: "er-input",
4121
+ value: wizard.formData.email,
4122
+ onChange: (event) => wizard.setFormData({
4123
+ ...wizard.formData,
4124
+ email: event.target.value
4125
+ }),
4126
+ ...getFieldValidationControlProps({
4127
+ message: wizard.errors.email,
4128
+ messageId: errorId
4129
+ })
4130
+ }),
4131
+ wizard.errors.email && /* @__PURE__ */ jsx(FieldValidationMessage, {
4132
+ id: errorId,
4133
+ className: "er-error",
4134
+ children: wizard.errors.email
4135
+ })
4136
+ ]
4137
+ });
4138
+ }
4139
+ function RecoveryEmail(props) {
4140
+ const { wizard } = props;
4141
+ return /* @__PURE__ */ jsxs("div", {
4142
+ className: "rb-reg-stack er-stack",
4143
+ children: [
4144
+ /* @__PURE__ */ jsx("p", {
4145
+ className: "er-text",
4146
+ children: "Already started a booking? Enter your email to finish paying."
4147
+ }),
4148
+ /* @__PURE__ */ jsx(RecoveryEmailField, { wizard }),
4149
+ props.showSpamProtection && /* @__PURE__ */ jsx("div", {
4150
+ className: "er-altcha",
4151
+ children: /* @__PURE__ */ jsx(AltchaWidget, {
4152
+ challengeUrl: getChallengeUrl(),
4153
+ onSolutionChange: wizard.setAltchaSolution
4154
+ })
4155
+ }),
4156
+ wizard.recoveryMessage && /* @__PURE__ */ jsx("p", {
4157
+ role: "alert",
4158
+ className: "er-error",
4159
+ children: wizard.recoveryMessage
4160
+ }),
4161
+ /* @__PURE__ */ jsx("button", {
4162
+ type: "button",
4163
+ className: "er-btn er-btn--primary",
4164
+ disabled: wizard.checkingNextStep || props.showSpamProtection && !wizard.altchaSolution,
4165
+ onClick: () => void wizard.handleRecoveryEmail(),
4166
+ children: wizard.checkingNextStep ? "Checking booking..." : "Continue"
4167
+ })
4168
+ ]
4169
+ });
4170
+ }
4171
+ function FullyBookedStep(props) {
4172
+ const { wizard } = props;
4173
+ const admission = wizard.admission;
4174
+ const byEmail = admission.kind === "fullyBooked" && admission.recovery === "byEmail";
4175
+ return /* @__PURE__ */ jsxs("div", {
4176
+ className: "rb-reg-stack er-stack",
4177
+ children: [
4178
+ /* @__PURE__ */ jsx("h3", {
4179
+ className: "er-title",
4180
+ children: "This event is fully booked"
4181
+ }),
4182
+ byEmail && /* @__PURE__ */ jsx(RecoveryEmail, { ...props }),
4183
+ /* @__PURE__ */ jsx("button", {
4184
+ type: "button",
4185
+ className: "er-back-btn",
4186
+ onClick: () => wizard.setStep("select-tickets"),
4187
+ children: "← Back"
4188
+ })
4189
+ ]
4190
+ });
4191
+ }
4192
+ //#endregion
3718
4193
  //#region ../blocks/src/system/runtime/nodes/event-registration/siteCreditPreview.ts
3719
4194
  function calculateEventRegistrationPreviewTotal(input) {
3720
4195
  const discountCents = input.manualDiscountCode.trim() ? 0 : input.automaticDiscount?.computedAmountCents ?? 0;
@@ -3790,7 +4265,7 @@ function buildSelectedSiteCreditPreview(input) {
3790
4265
  function resolvePaymentStepStatus(input) {
3791
4266
  if (input.loading) return "loading";
3792
4267
  if (input.error || !input.eligibility) return "error";
3793
- if (input.eligibility.registration.kind === "waitlisted" || input.eligibility.paymentOptions.length === 0) return "not-eligible";
4268
+ if (resolveRegistrationAvailabilityBlocker(input.admission) !== null || input.eligibility.paymentOptions.length === 0) return "not-eligible";
3794
4269
  return "ready";
3795
4270
  }
3796
4271
  function shouldPromptForLogin(status, isAuthenticated, eligibility) {
@@ -3824,8 +4299,8 @@ function UnavailableEntitlementMessage(props) {
3824
4299
  })
3825
4300
  ] });
3826
4301
  }
3827
- function IneligiblePaymentPanel(props) {
3828
- if (isSelectionSoldOut(props.eligibility.availability)) return /* @__PURE__ */ jsxs("div", {
4302
+ function SoldOutPaymentPanel() {
4303
+ return /* @__PURE__ */ jsxs("div", {
3829
4304
  className: "rb-reg-stack er-stack",
3830
4305
  children: [/* @__PURE__ */ jsx("h3", {
3831
4306
  className: "er-title",
@@ -3838,6 +4313,8 @@ function IneligiblePaymentPanel(props) {
3838
4313
  })
3839
4314
  })]
3840
4315
  });
4316
+ }
4317
+ function IneligiblePaymentPanel(props) {
3841
4318
  const promptLogin = shouldPromptForLogin("not-eligible", props.isAuthenticated, props.eligibility);
3842
4319
  return /* @__PURE__ */ jsxs("div", {
3843
4320
  className: "rb-reg-stack er-stack",
@@ -3867,6 +4344,12 @@ function IneligiblePaymentPanel(props) {
3867
4344
  })]
3868
4345
  });
3869
4346
  }
4347
+ function renderRegistrationAvailabilityBlocker(admission) {
4348
+ const blocker = resolveRegistrationAvailabilityBlocker(admission);
4349
+ if (blocker?.kind !== "unavailable") return null;
4350
+ if (admission.kind === "closed" && admission.reason === "alreadyWaitlisted") return /* @__PURE__ */ jsx(AlreadyWaitlistedPaymentPanel, {});
4351
+ return admission.kind === "fullyBooked" ? /* @__PURE__ */ jsx(SoldOutPaymentPanel, {}) : /* @__PURE__ */ jsx(ClosedPaymentPanel, { message: blocker.message });
4352
+ }
3870
4353
  function PaymentBlockingPanel(props) {
3871
4354
  if (props.eligibilityLoading) return /* @__PURE__ */ jsx("div", {
3872
4355
  className: "rb-reg-stack er-stack",
@@ -3892,7 +4375,18 @@ function PaymentBlockingPanel(props) {
3892
4375
  })
3893
4376
  });
3894
4377
  if (!props.eligibility) return /* @__PURE__ */ jsx(MissingEligibilityPanel, {});
3895
- if (props.eligibility.registration.kind === "waitlisted") return /* @__PURE__ */ jsxs("div", {
4378
+ const availabilityPanel = renderRegistrationAvailabilityBlocker(props.admission);
4379
+ if (availabilityPanel) return availabilityPanel;
4380
+ if (props.eligibility.isFree) return /* @__PURE__ */ jsx(FreePaymentSummary, { ticketsCount: props.ticketsCount });
4381
+ return props.eligibility.paymentOptions.length > 0 ? null : /* @__PURE__ */ jsx(IneligiblePaymentPanel, {
4382
+ eligibility: props.eligibility,
4383
+ email: props.email,
4384
+ isAuthenticated: props.isAuthenticated,
4385
+ onRequestLogin: props.onRequestLogin
4386
+ });
4387
+ }
4388
+ function AlreadyWaitlistedPaymentPanel() {
4389
+ return /* @__PURE__ */ jsxs("div", {
3896
4390
  className: "rb-reg-stack er-stack",
3897
4391
  children: [/* @__PURE__ */ jsx("h3", {
3898
4392
  className: "er-title",
@@ -3909,7 +4403,9 @@ function PaymentBlockingPanel(props) {
3909
4403
  })]
3910
4404
  })]
3911
4405
  });
3912
- if (props.eligibility.isFree) return /* @__PURE__ */ jsxs("div", {
4406
+ }
4407
+ function FreePaymentSummary({ ticketsCount }) {
4408
+ return /* @__PURE__ */ jsxs("div", {
3913
4409
  className: "rb-reg-stack er-stack",
3914
4410
  children: [/* @__PURE__ */ jsx("h3", {
3915
4411
  className: "er-title",
@@ -3922,18 +4418,24 @@ function PaymentBlockingPanel(props) {
3922
4418
  }), /* @__PURE__ */ jsxs("p", {
3923
4419
  className: "er-text er-text--muted",
3924
4420
  children: [
3925
- props.ticketsCount,
4421
+ ticketsCount,
3926
4422
  " ticket",
3927
- props.ticketsCount > 1 ? "s" : ""
4423
+ ticketsCount > 1 ? "s" : ""
3928
4424
  ]
3929
4425
  })]
3930
4426
  })]
3931
4427
  });
3932
- return props.eligibility.paymentOptions.length > 0 ? null : /* @__PURE__ */ jsx(IneligiblePaymentPanel, {
3933
- eligibility: props.eligibility,
3934
- email: props.email,
3935
- isAuthenticated: props.isAuthenticated,
3936
- onRequestLogin: props.onRequestLogin
4428
+ }
4429
+ function ClosedPaymentPanel({ message }) {
4430
+ return /* @__PURE__ */ jsx("div", {
4431
+ className: "rb-reg-stack er-stack",
4432
+ children: /* @__PURE__ */ jsx("div", {
4433
+ className: "er-center",
4434
+ children: /* @__PURE__ */ jsx("p", {
4435
+ className: "er-text er-text--error",
4436
+ children: message
4437
+ })
4438
+ })
3937
4439
  });
3938
4440
  }
3939
4441
  //#endregion
@@ -4919,8 +5421,9 @@ function canContinueWithPaymentSelection(selection, directPaymentSelection) {
4919
5421
  case null: return false;
4920
5422
  }
4921
5423
  }
4922
- function buildEventDirectPaymentOptions(input) {
4923
- return buildEventRegistrationPaymentOptionsViewModel({
5424
+ function buildEventPaymentSelectionView(input) {
5425
+ const canShowDirectPaymentOptions = Boolean(input.eligibility?.requiresPayment && input.eligibility.price);
5426
+ const directPaymentOptions = input.eligibility && canShowDirectPaymentOptions ? buildEventRegistrationPaymentOptionsViewModel({
4924
5427
  eventTitle: input.event?.title ?? "Event",
4925
5428
  approvedPaymentOptions: input.eligibility.paymentOptions,
4926
5429
  paymentTerms: input.eligibility.paymentTerms,
@@ -4928,7 +5431,18 @@ function buildEventDirectPaymentOptions(input) {
4928
5431
  currency: input.eligibility.currency,
4929
5432
  startsAt: getRegistrationStartAt(input.event, input.occurrenceContext),
4930
5433
  nowIso: input.nowIso
4931
- }).options;
5434
+ }).options : [];
5435
+ const directPaymentSelectionState = resolveEventRegistrationDirectPaymentSelection({
5436
+ options: directPaymentOptions,
5437
+ selection: input.selection
5438
+ });
5439
+ return {
5440
+ canShowDirectPaymentOptions,
5441
+ directPaymentOptions,
5442
+ directPaymentSelectionState,
5443
+ directPaymentFallbackKind: directPaymentSelectionState.kind === "invalid" ? directPaymentSelectionState.fallbackSelection.collectionOptionKind : void 0,
5444
+ selectedDirectOption: directPaymentSelectionState.kind === "valid" ? directPaymentOptions.find((option) => option.kind === directPaymentSelectionState.selection.collectionOptionKind) ?? null : null
5445
+ };
4932
5446
  }
4933
5447
  /**
4934
5448
  * Payment Selection Step
@@ -4937,24 +5451,17 @@ function buildEventDirectPaymentOptions(input) {
4937
5451
  * Fetches eligible passes/memberships using the user's email.
4938
5452
  * Also shows available passes/memberships for purchase (upsell).
4939
5453
  */
4940
- const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, isAuthenticated = false, creditState = { kind: "not_authenticated" }, directPaymentTotalCents, manualDiscountPending = false, selectedSelection, onSelectionChange, eligibility, eligibilityLoading, eligibilityError = null, onRequestLogin, onStateChange, nowIso }) => {
5454
+ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, isAuthenticated = false, creditState = { kind: "not_authenticated" }, directPaymentTotalCents, manualDiscountPending = false, selectedSelection, onSelectionChange, eligibility, admission, eligibilityLoading, eligibilityError = null, onRequestLogin, onStateChange, nowIso }) => {
4941
5455
  const occurrenceId = occurrenceContext?.id ?? (event?.registrationSubject.kind === "occurrence" ? event.registrationSubject.occurrenceId : "");
4942
5456
  const selectedMethod = selectedSelection.method;
4943
- const totalPrice = directPaymentTotalCents;
4944
- const canShowDirectPaymentOptions = Boolean(eligibility?.requiresPayment && eligibility.price);
4945
- const directPaymentOptions = eligibility && canShowDirectPaymentOptions ? buildEventDirectPaymentOptions({
5457
+ const { canShowDirectPaymentOptions, directPaymentOptions, directPaymentSelectionState, directPaymentFallbackKind, selectedDirectOption } = buildEventPaymentSelectionView({
4946
5458
  event,
4947
5459
  occurrenceContext,
4948
5460
  eligibility,
4949
- totalPriceCents: totalPrice,
4950
- nowIso
4951
- }) : [];
4952
- const directPaymentSelectionState = resolveEventRegistrationDirectPaymentSelection({
4953
- options: directPaymentOptions,
5461
+ totalPriceCents: directPaymentTotalCents,
5462
+ nowIso,
4954
5463
  selection: selectedSelection
4955
5464
  });
4956
- const directPaymentFallbackKind = (directPaymentSelectionState.kind === "invalid" ? directPaymentSelectionState.fallbackSelection : null)?.collectionOptionKind;
4957
- const selectedDirectOption = directPaymentSelectionState.kind === "valid" ? directPaymentOptions.find((option) => option.kind === directPaymentSelectionState.selection.collectionOptionKind) ?? null : null;
4958
5465
  const siteCreditPreview = buildSelectedSiteCreditPreview({
4959
5466
  options: directPaymentOptions,
4960
5467
  selectionState: directPaymentSelectionState,
@@ -4973,7 +5480,8 @@ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, i
4973
5480
  const paymentStepStatus = resolvePaymentStepStatus({
4974
5481
  loading: eligibilityLoading,
4975
5482
  error: eligibilityError,
4976
- eligibility
5483
+ eligibility,
5484
+ admission
4977
5485
  });
4978
5486
  const paymentButtonText = resolvePaymentButtonText(selectedMethod, siteCreditPreview, eligibility, selectedDirectOption);
4979
5487
  const showLoginPrompt = shouldPromptForLogin(paymentStepStatus, isAuthenticated, eligibility);
@@ -4994,6 +5502,7 @@ const PaymentSelectionStep = ({ event, occurrenceContext, ticketsCount, email, i
4994
5502
  ]);
4995
5503
  const blockingPanel = /* @__PURE__ */ jsx(PaymentBlockingPanel, {
4996
5504
  eligibility,
5505
+ admission,
4997
5506
  eligibilityLoading,
4998
5507
  eligibilityError,
4999
5508
  occurrenceId,
@@ -5141,16 +5650,6 @@ function ExistingRegistrationNotice(props) {
5141
5650
  })
5142
5651
  });
5143
5652
  }
5144
- function resolveExistingRegistrationKind(wizard) {
5145
- return wizard.registrationState.kind === "waitlisted" ? "waitlisted" : "booked";
5146
- }
5147
- function isExistingRegistrationBlocking(wizard) {
5148
- return hasBlockingExistingRegistration({
5149
- isAuthenticated: wizard.portalAuthState === "authenticated",
5150
- registration: wizard.registrationState,
5151
- hasWaitlistClaim: wizard.waitlistClaim.claim !== null
5152
- });
5153
- }
5154
5653
  function AttendeeDetailsContent(props) {
5155
5654
  const { wizard } = props;
5156
5655
  const membership = wizard.portalAuthState === "authenticated" && wizard.ticketsCount === 1 ? wizard.eligibility?.eligibleMemberships[0] : void 0;
@@ -5203,6 +5702,7 @@ function PaymentSelectionContent(props) {
5203
5702
  if (!wizard.api.config.siteId) return null;
5204
5703
  if (subtotalCents === null) return /* @__PURE__ */ jsx(PaymentBlockingPanel, {
5205
5704
  eligibility: wizard.eligibility,
5705
+ admission: wizard.admission,
5206
5706
  eligibilityLoading: wizard.eligibilityLoading,
5207
5707
  eligibilityError: wizard.eligibilityError,
5208
5708
  occurrenceId: wizard.occurrenceContext?.id ?? "",
@@ -5227,6 +5727,7 @@ function PaymentSelectionContent(props) {
5227
5727
  selectedSelection: wizard.paymentSelection,
5228
5728
  onSelectionChange: wizard.handlePaymentSelectionChange,
5229
5729
  eligibility: wizard.eligibility,
5730
+ admission: wizard.admission,
5230
5731
  eligibilityLoading: wizard.eligibilityLoading,
5231
5732
  eligibilityError: wizard.eligibilityError,
5232
5733
  onRequestLogin: () => wizard.setShowMagicLinkModal(true),
@@ -5234,14 +5735,54 @@ function PaymentSelectionContent(props) {
5234
5735
  nowIso: wizard.nowIso
5235
5736
  }), /* @__PURE__ */ jsx(EventDiscountCodeDisclosure, { wizard })] });
5236
5737
  }
5738
+ function ClosedAdmissionNotice({ wizard }) {
5739
+ const admission = wizard.admission;
5740
+ return admission.kind === "closed" && admission.reason !== "alreadyWaitlisted" && !wizard.eligibilityLoading ? /* @__PURE__ */ jsx("p", {
5741
+ role: "alert",
5742
+ className: "er-error",
5743
+ children: admission.message
5744
+ }) : null;
5745
+ }
5746
+ function TicketSelectionContent({ wizard }) {
5747
+ if (!wizard.selectedEvent && !wizard.occurrenceContext) return null;
5748
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5749
+ wizard.admission.kind === "closed" && wizard.admission.reason === "alreadyWaitlisted" && /* @__PURE__ */ jsx(ExistingRegistrationNotice, {
5750
+ kind: "waitlisted",
5751
+ onBookForSomeoneElse: wizard.handleBookForSomeoneElse
5752
+ }),
5753
+ /* @__PURE__ */ jsx(ClosedAdmissionNotice, { wizard }),
5754
+ /* @__PURE__ */ jsx(TicketSelectionStep, {
5755
+ event: wizard.selectedEvent,
5756
+ occurrenceContext: wizard.occurrenceContext,
5757
+ selectedAttendanceMode: wizard.selectedAttendanceMode,
5758
+ onAttendanceModeChange: wizard.setSelectedAttendanceMode,
5759
+ ticketsCount: wizard.ticketsCount,
5760
+ maxTickets: wizard.maxTicketsNum,
5761
+ visibleTicketTypes: wizard.visibleTicketTypes,
5762
+ ticketSelections: wizard.selectedTicketLines,
5763
+ onTicketSelectionsChange: wizard.setSelectedTicketLines,
5764
+ slidingScale: wizard.selectedEventSlidingScale,
5765
+ selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
5766
+ slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
5767
+ onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
5768
+ onTicketsChange: wizard.setTicketsCount,
5769
+ bookingUnit: resolveBookingUnit(wizard.selectedEvent)
5770
+ })
5771
+ ] });
5772
+ }
5237
5773
  function EventRegistrationWizardContent(props) {
5238
5774
  const { wizard } = props;
5239
5775
  const showSpamProtection = props.showSpamProtection ?? isSpamProtectionEnabled();
5240
5776
  switch (wizard.step) {
5777
+ case "payment": return /* @__PURE__ */ jsx(EventPaymentMount, {
5778
+ state: wizard.paymentController.state,
5779
+ onComplete: wizard.paymentController.check,
5780
+ onRetry: wizard.paymentController.retryPaymentLoad,
5781
+ onBack: wizard.paymentController.backToBooking
5782
+ });
5241
5783
  case "error":
5242
5784
  case "success":
5243
5785
  case "submitting":
5244
- case "redirecting":
5245
5786
  case "verifying-payment":
5246
5787
  case "verifying-timeout":
5247
5788
  case "payment-status":
@@ -5253,28 +5794,11 @@ function EventRegistrationWizardContent(props) {
5253
5794
  showVenue: props.showVenue,
5254
5795
  showCapacity: props.showCapacity
5255
5796
  });
5256
- case "select-tickets":
5257
- if (!wizard.selectedEvent && !wizard.occurrenceContext) return null;
5258
- return /* @__PURE__ */ jsxs(Fragment, { children: [isExistingRegistrationBlocking(wizard) && /* @__PURE__ */ jsx(ExistingRegistrationNotice, {
5259
- kind: resolveExistingRegistrationKind(wizard),
5260
- onBookForSomeoneElse: wizard.handleBookForSomeoneElse
5261
- }), /* @__PURE__ */ jsx(TicketSelectionStep, {
5262
- event: wizard.selectedEvent,
5263
- occurrenceContext: wizard.occurrenceContext,
5264
- selectedAttendanceMode: wizard.selectedAttendanceMode,
5265
- onAttendanceModeChange: wizard.setSelectedAttendanceMode,
5266
- ticketsCount: wizard.ticketsCount,
5267
- maxTickets: wizard.maxTicketsNum,
5268
- visibleTicketTypes: wizard.visibleTicketTypes,
5269
- ticketSelections: wizard.selectedTicketLines,
5270
- onTicketSelectionsChange: wizard.setSelectedTicketLines,
5271
- slidingScale: wizard.selectedEventSlidingScale,
5272
- selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
5273
- slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
5274
- onSlidingAmountChange: wizard.setSelectedSlidingAmountInput,
5275
- onTicketsChange: wizard.setTicketsCount,
5276
- bookingUnit: resolveBookingUnit(wizard.selectedEvent)
5277
- })] });
5797
+ case "select-tickets": return /* @__PURE__ */ jsx(TicketSelectionContent, { wizard });
5798
+ case "fully-booked": return /* @__PURE__ */ jsx(FullyBookedStep, {
5799
+ wizard,
5800
+ showSpamProtection
5801
+ });
5278
5802
  case "enter-details": return /* @__PURE__ */ jsx(AttendeeDetailsContent, {
5279
5803
  wizard,
5280
5804
  showSpamProtection
@@ -5353,7 +5877,10 @@ function presentation(state) {
5353
5877
  case "assistanceRequired": return {
5354
5878
  title: "We still can’t confirm your payment",
5355
5879
  message: "Please don’t make another payment. Contact the organiser so they can check your booking.",
5356
- action: { label: "Return to booking" }
5880
+ action: {
5881
+ label: "Return to booking",
5882
+ disabled: true
5883
+ }
5357
5884
  };
5358
5885
  }
5359
5886
  }
@@ -5604,51 +6131,27 @@ function EventRegistrationMagicLinkModal(props) {
5604
6131
  function EventRegistrationTerminalState(props) {
5605
6132
  const { wizard } = props;
5606
6133
  switch (wizard.step) {
5607
- case "submitting":
5608
- case "redirecting": {
5609
- const label = wizard.step === "redirecting" ? "Redirecting to payment" : "Processing your registration";
5610
- return /* @__PURE__ */ jsxs("div", {
5611
- className: "er-center",
5612
- children: [/* @__PURE__ */ jsx(SpinnerNode, {
5613
- size: "lg",
5614
- srLabel: label
5615
- }), /* @__PURE__ */ jsxs("p", {
5616
- className: "er-text--muted",
5617
- children: [label, "..."]
5618
- })]
5619
- });
5620
- }
6134
+ case "submitting": return /* @__PURE__ */ jsx(RegistrationProcessing, {});
6135
+ case "payment": return null;
5621
6136
  case "verifying-payment": return /* @__PURE__ */ jsx(VerifyingPaymentStep, {});
5622
6137
  case "payment-failed": return /* @__PURE__ */ jsx(PaymentFailedStep, {
5623
6138
  message: wizard.errorMessage,
5624
6139
  onRetry: () => wizard.setStep("select-payment")
5625
6140
  });
5626
- case "verifying-timeout": return /* @__PURE__ */ jsx(VerifyingTimeoutStep, {
5627
- onCheckAgain: wizard.verification.status === "timeout" ? wizard.verification.retry : wizard.verification.retryReturn,
5628
- title: wizard.verification.status === "timeout" ? void 0 : "We’re checking your payment",
5629
- message: wizard.verification.status === "timeout" ? void 0 : wizard.errorMessage,
6141
+ case "verifying-timeout": return /* @__PURE__ */ jsx(RegistrationVerificationTimeout, {
6142
+ wizard,
5630
6143
  supportEmail: props.supportEmail
5631
6144
  });
5632
- case "payment-status": return wizard.paymentStatusRecovery ? /* @__PURE__ */ jsx(PaymentStatusStep, {
5633
- state: wizard.paymentStatusRecovery,
5634
- onCheck: wizard.handleCheckPaymentStatus,
5635
- onContinuePayment: wizard.handleContinueExistingPayment,
5636
- onReturnToPayment: wizard.handleReturnToPayment,
5637
- onReturnToBooking: wizard.handleReturnToBooking
5638
- }) : null;
6145
+ case "payment-status": return /* @__PURE__ */ jsx(RegistrationPaymentStatus, { wizard });
5639
6146
  case "success": return /* @__PURE__ */ jsx(EventRegistrationSuccess, {
5640
6147
  wizard,
5641
6148
  fallbackMessage: props.successMessage,
5642
6149
  demoEmailSuppressed: props.demoEmailSuppressed
5643
6150
  });
5644
- case "error": return /* @__PURE__ */ jsx(ErrorStep, {
5645
- message: wizard.errorMessage,
5646
- action: wizard.errorAction,
5647
- canJoinWaitlist: wizard.eligibility?.availability.kind === "waitlist" && wizard.eligibility.registration.kind !== "pending_payment",
5648
- onRetry: wizard.handleRetry
5649
- });
6151
+ case "error": return /* @__PURE__ */ jsx(RegistrationError, { wizard });
5650
6152
  case "select-event":
5651
6153
  case "select-tickets":
6154
+ case "fully-booked":
5652
6155
  case "enter-details":
5653
6156
  case "select-payment": return null;
5654
6157
  }
@@ -5717,6 +6220,46 @@ function EventRegistrationSurface(props) {
5717
6220
  })]
5718
6221
  });
5719
6222
  }
6223
+ function RegistrationProcessing() {
6224
+ const label = "Processing your registration";
6225
+ return /* @__PURE__ */ jsxs("div", {
6226
+ className: "er-center",
6227
+ children: [/* @__PURE__ */ jsx(SpinnerNode, {
6228
+ size: "lg",
6229
+ srLabel: label
6230
+ }), /* @__PURE__ */ jsxs("p", {
6231
+ className: "er-text--muted",
6232
+ children: [label, "..."]
6233
+ })]
6234
+ });
6235
+ }
6236
+ function RegistrationVerificationTimeout({ wizard, supportEmail }) {
6237
+ const timedOut = wizard.verification.status === "timeout";
6238
+ return /* @__PURE__ */ jsx(VerifyingTimeoutStep, {
6239
+ onCheckAgain: timedOut ? wizard.verification.retry : wizard.verification.retryReturn,
6240
+ title: timedOut ? void 0 : "We’re checking your payment",
6241
+ message: timedOut ? void 0 : wizard.errorMessage,
6242
+ supportEmail
6243
+ });
6244
+ }
6245
+ function RegistrationPaymentStatus({ wizard }) {
6246
+ return wizard.paymentStatusRecovery ? /* @__PURE__ */ jsx(PaymentStatusStep, {
6247
+ state: wizard.paymentStatusRecovery,
6248
+ onCheck: wizard.handleCheckPaymentStatus,
6249
+ onContinuePayment: wizard.handleContinueExistingPayment,
6250
+ onReturnToPayment: wizard.handleReturnToPayment,
6251
+ onReturnToBooking: wizard.handleReturnToBooking
6252
+ }) : null;
6253
+ }
6254
+ function RegistrationError({ wizard }) {
6255
+ const admission = wizard.admission;
6256
+ return /* @__PURE__ */ jsx(ErrorStep, {
6257
+ message: wizard.errorMessage,
6258
+ action: wizard.errorAction,
6259
+ canJoinWaitlist: admission.kind === "waitlist",
6260
+ onRetry: wizard.handleRetry
6261
+ });
6262
+ }
5720
6263
  //#endregion
5721
6264
  //#region ../blocks/src/system/runtime/nodes/event-registration/EventRegistrationWizard.tsx
5722
6265
  const EVENT_PORTAL_AUTH_COPY = {
@@ -5764,11 +6307,12 @@ function getWizardStepLabel(input) {
5764
6307
  case "select-event": return input.eventSelectionLabel;
5765
6308
  case "select-tickets": return input.selectedEvent?.registrationSubject.kind === "wholeSeries" ? "Places" : "Tickets";
5766
6309
  case "enter-details": return "Details";
5767
- case "select-payment": return "Payment";
6310
+ case "select-payment": return "Payment options";
6311
+ case "payment": return "Payment";
6312
+ case "fully-booked":
5768
6313
  case "error":
5769
6314
  case "success":
5770
6315
  case "submitting":
5771
- case "redirecting":
5772
6316
  case "verifying-payment":
5773
6317
  case "verifying-timeout":
5774
6318
  case "payment-status":
@@ -5799,7 +6343,6 @@ function EventRegistrationWizardView(viewProps) {
5799
6343
  const showSpamProtection = spamProtectionEnabled ?? isSpamProtectionEnabled();
5800
6344
  const registrationRootClassName = `event-registration ${className || ""}`;
5801
6345
  const registrationRootRef = React.useRef(null);
5802
- useEventCheckoutHandoffRestore(wizard.step === "redirecting");
5803
6346
  const [bookingOverlayOpen, setBookingOverlayOpen] = useState(false);
5804
6347
  const [floatingPortalRoot, setFloatingPortalRoot] = useState(null);
5805
6348
  useEffect(() => {
@@ -5839,19 +6382,19 @@ function EventRegistrationWizardView(viewProps) {
5839
6382
  })
5840
6383
  })
5841
6384
  });
5842
- const isWizardStep = WIZARD_STEPS.includes(wizard.step);
5843
6385
  const activeWizardSteps = useMemo(() => {
5844
6386
  const needsPaymentStep = requiresPaymentStep({
5845
6387
  eventRequiresPayment: wizard.eventRequiresPayment,
5846
6388
  paymentSelection: wizard.paymentSelection
5847
6389
  });
5848
- return getWizardStepsForContext(wizard.hasPreSelectedContext, needsPaymentStep);
6390
+ return getWizardStepsForContext(wizard.hasPreSelectedContext, needsPaymentStep, wizard.step === "payment");
5849
6391
  }, [
6392
+ wizard.step,
5850
6393
  wizard.hasPreSelectedContext,
5851
6394
  wizard.eventRequiresPayment,
5852
6395
  wizard.paymentSelection
5853
6396
  ]);
5854
- const totalSteps = activeWizardSteps.length;
6397
+ const { isWizardStep, totalSteps } = getRegistrationStepPresentation(wizard.step, activeWizardSteps);
5855
6398
  const summaryEvent = wizard.selectedEvent ?? wizard.preSelectedEvent;
5856
6399
  const eventSelectionStepLabel = getEventSelectionMode(wizard.scopedEvents) === "date" ? "Date" : "Event";
5857
6400
  const stepLabels = activeWizardSteps.map((step) => getWizardStepLabel({
@@ -5911,7 +6454,10 @@ function EventRegistrationWizardView(viewProps) {
5911
6454
  ticketCurrency: bookingCurrency,
5912
6455
  buttonVariant,
5913
6456
  formatTicketPrice: (ticketType) => formatTicketPriceIfKnown(ticketType, bookingCurrency),
5914
- onOpen: () => setBookingOverlayOpen(true),
6457
+ onOpen: () => {
6458
+ if (wizard.paymentController.state.kind === "payment") wizard.paymentController.retryPaymentLoad();
6459
+ setBookingOverlayOpen(true);
6460
+ },
5915
6461
  onClose: () => setBookingOverlayOpen(false),
5916
6462
  overlaySurface: renderRegistrationSurface("overlay")
5917
6463
  })]