@riverbankcms/sdk 0.94.0 → 0.96.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 (189) hide show
  1. package/README.md +30 -4
  2. package/dist/{PreviewEditorSidebar-D3PAF7NW.mjs → PreviewEditorSidebar-46VT4QVU.mjs} +3 -3
  3. package/dist/{PreviewEditorUI-G5JVJEIJ.mjs → PreviewEditorUI-JD75GE5F.mjs} +3 -3
  4. package/dist/SdkPreviewModeRuntime-MJUSYUFB.mjs +8 -0
  5. package/dist/_dts/ai/src/contracts/proposals.d.ts +10 -10
  6. package/dist/_dts/api/src/bookingOperations.d.ts +7 -0
  7. package/dist/_dts/api/src/bookingRecords.d.ts +3 -0
  8. package/dist/_dts/api/src/cache-tags.d.ts +4 -0
  9. package/dist/_dts/api/src/contentRuntime.d.ts +14 -0
  10. package/dist/_dts/api/src/endpoints.d.ts +8 -0
  11. package/dist/_dts/api/src/event-presentation.d.ts +15 -0
  12. package/dist/_dts/api/src/index.d.ts +1 -1
  13. package/dist/_dts/api/src/public/endpoints.d.ts +1 -1
  14. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +17 -4
  15. package/dist/_dts/api/src/shared-contracts.d.ts +19 -9
  16. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +6 -1
  17. package/dist/_dts/block-form/src/client/index.d.ts +1 -1
  18. package/dist/_dts/block-form/src/fields/types.d.ts +9 -1
  19. package/dist/_dts/block-form/src/registry/schemas.d.ts +4 -0
  20. package/dist/_dts/block-form/src/registry/widgets.d.ts +1 -0
  21. package/dist/_dts/block-form/src/widgets/InlineTextField/InlineTextField.d.ts +4 -0
  22. package/dist/_dts/block-form/src/widgets/InlineTextField/index.d.ts +3 -0
  23. package/dist/_dts/block-form/src/widgets/InlineTextField/inlineTextFieldDefaults.d.ts +2 -0
  24. package/dist/_dts/block-form/src/widgets/InlineTextField/inlineTextFieldSchema.d.ts +3 -0
  25. package/dist/_dts/blocks/src/bindings/index.d.ts +1 -0
  26. package/dist/_dts/blocks/src/bindings/types.d.ts +1 -0
  27. package/dist/_dts/blocks/src/index.d.ts +2 -0
  28. package/dist/_dts/blocks/src/portal/types.d.ts +7 -0
  29. package/dist/_dts/blocks/src/system/blocks/appointment-booking.d.ts +2 -29631
  30. package/dist/_dts/blocks/src/system/blocks/blog-listing.d.ts +2 -29614
  31. package/dist/_dts/blocks/src/system/blocks/cart.d.ts +2 -29634
  32. package/dist/_dts/blocks/src/system/blocks/checkout.d.ts +2 -29627
  33. package/dist/_dts/blocks/src/system/blocks/collection.d.ts +2 -29585
  34. package/dist/_dts/blocks/src/system/blocks/faq.d.ts +2 -29677
  35. package/dist/_dts/blocks/src/system/blocks/gifting.d.ts +2 -29655
  36. package/dist/_dts/blocks/src/system/blocks/media-text.d.ts +220 -0
  37. package/dist/_dts/blocks/src/system/blocks/product-detail.d.ts +2 -29655
  38. package/dist/_dts/blocks/src/system/blocks/product-list.d.ts +2 -29685
  39. package/dist/_dts/blocks/src/system/blocks/shop.d.ts +2 -29761
  40. package/dist/_dts/blocks/src/system/fragments/library/heroCopy.d.ts +9 -0
  41. package/dist/_dts/blocks/src/system/inlineText.d.ts +78 -0
  42. package/dist/_dts/blocks/src/system/manifest/derivedContent.d.ts +3 -1
  43. package/dist/_dts/blocks/src/system/manifest/fieldValidation/index.d.ts +15 -3
  44. package/dist/_dts/blocks/src/system/manifest/schema.d.ts +188 -7
  45. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +1 -1
  46. package/dist/_dts/blocks/src/system/runtime/api/types.d.ts +21 -6
  47. package/dist/_dts/blocks/src/system/runtime/nodes/basic.d.ts +2 -1
  48. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +5 -3
  49. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +5 -1
  50. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +2 -2
  51. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/sessionStorage.d.ts +9 -6
  52. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.d.ts +36 -0
  53. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +7 -1
  54. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +2 -2
  55. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +2 -3
  56. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +19 -4
  57. package/dist/_dts/blocks/src/system/runtime/nodes/renderInlineText.d.ts +3 -0
  58. package/dist/_dts/content-editor/src/index.d.ts +1 -0
  59. package/dist/_dts/content-editor/src/primitives/PreviewInlineTextEditor.d.ts +9 -0
  60. package/dist/_dts/content-model/src/entry-slots/types.d.ts +6 -0
  61. package/dist/_dts/content-model/src/templates/bindingTypeCompatibility.d.ts +1 -1
  62. package/dist/_dts/content-model/src/templates/defaults.d.ts +30 -0
  63. package/dist/_dts/content-model/src/templates/types.d.ts +1 -0
  64. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +163 -2
  65. package/dist/_dts/editor/src/inline/FloatingToolbar.d.ts +18 -0
  66. package/dist/_dts/editor/src/inline/InlineTextEditor.d.ts +18 -1
  67. package/dist/_dts/editor/src/inline/InlineTextEditorStyles.d.ts +1 -0
  68. package/dist/_dts/editor/src/inline/domParse.d.ts +3 -0
  69. package/dist/_dts/editor/src/inline/presentation.d.ts +48 -0
  70. package/dist/_dts/editor/src/inline/selection.d.ts +12 -0
  71. package/dist/_dts/editor-blocks/src/widgets/index.d.ts +1 -0
  72. package/dist/_dts/editor-blocks/src/widgets/inline-text/InlineTextField.d.ts +3 -0
  73. package/dist/_dts/editor-blocks/src/widgets/inline-text/index.d.ts +1 -0
  74. package/dist/_dts/media-core/src/assetsInUseSummary.d.ts +7 -0
  75. package/dist/_dts/media-core/src/mediaUsageProjection.d.ts +29 -1
  76. package/dist/_dts/path-utils/src/index.d.ts +1 -0
  77. package/dist/_dts/preview-next/src/client/widgets/widgetOverrides.d.ts +1 -0
  78. package/dist/_dts/sdk/src/cli/commands/content.d.ts +56 -0
  79. package/dist/_dts/sdk/src/cli/commands/push/options.d.ts +4 -0
  80. package/dist/_dts/sdk/src/cli/commands/push/scopes/commandBackedDatedOfferingScope.d.ts +2 -6
  81. package/dist/_dts/sdk/src/cli/commands/push/scopes/types.d.ts +2 -0
  82. package/dist/_dts/sdk/src/cli/commands/push/verification.d.ts +56 -0
  83. package/dist/_dts/sdk/src/cli/commands/push-execute.d.ts +2 -1
  84. package/dist/_dts/sdk/src/cli/content/edit/format.d.ts +2 -0
  85. package/dist/_dts/sdk/src/cli/content/edit/path.d.ts +14 -0
  86. package/dist/_dts/sdk/src/cli/content/edit/planner.d.ts +46 -0
  87. package/dist/_dts/sdk/src/cli/content/fs-utils.d.ts +4 -0
  88. package/dist/_dts/sdk/src/cli/content/writer.d.ts +2 -0
  89. package/dist/_dts/sdk/src/cli/merge-remote/mergeFieldChanges.d.ts +22 -0
  90. package/dist/_dts/sdk/src/cli/merge-remote/pageLocal.d.ts +3 -0
  91. package/dist/_dts/sdk/src/cli/merge-remote/pageMerge.d.ts +78 -0
  92. package/dist/_dts/sdk/src/cli/merge-remote/pageMergePlan.d.ts +36 -0
  93. package/dist/_dts/sdk/src/cli/merge-remote/pageMergePush.d.ts +21 -0
  94. package/dist/_dts/sdk/src/cli/merge-remote/pageMergeReport.d.ts +29 -0
  95. package/dist/_dts/sdk/src/cli/merge-remote/pageSnapshots.d.ts +48 -0
  96. package/dist/_dts/sdk/src/cli/sync/diff.d.ts +3 -6
  97. package/dist/_dts/sdk/src/cli/sync/field-diff.d.ts +39 -0
  98. package/dist/_dts/sdk/src/cli/sync/index.d.ts +1 -0
  99. package/dist/_dts/sdk/src/client/types.d.ts +7 -2
  100. package/dist/_dts/sdk/src/config/field-definitions.d.ts +8 -3
  101. package/dist/_dts/sdk/src/config/typed-entries.d.ts +5 -3
  102. package/dist/_dts/sdk/src/contracts/bindings.d.ts +1 -0
  103. package/dist/_dts/sdk/src/contracts/content.d.ts +18 -0
  104. package/dist/_dts/sdk/src/contracts/index.d.ts +1 -1
  105. package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +3 -2
  106. package/dist/_dts/sdk/src/next/middleware.d.ts +1 -0
  107. package/dist/_dts/sdk/src/next/redirect-destination.d.ts +3 -0
  108. package/dist/_dts/sdk/src/next/route-outcome-proxy.d.ts +22 -0
  109. package/dist/_dts/sdk/src/public-api/contracts.d.ts +20 -1
  110. package/dist/_dts/sdk/src/public-api/runtime.d.ts +1 -1
  111. package/dist/_dts/sdk/src/rendering/helpers/bindings.d.ts +1 -0
  112. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +9 -1
  113. package/dist/_dts/sdk/src/version.d.ts +1 -1
  114. package/dist/_dts/theme-core/src/accordions/types.d.ts +1 -1
  115. package/dist/_dts/theme-core/src/buttons/types.d.ts +1 -1
  116. package/dist/_dts/theme-core/src/cards/types.d.ts +15 -15
  117. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  118. package/dist/_dts/theme-core/src/inputs/types.d.ts +1 -1
  119. package/dist/_dts/theme-core/src/mock-themes/all.d.ts +42 -42
  120. package/dist/_dts/theme-core/src/palette/variants/types.d.ts +2 -2
  121. package/dist/_dts/theme-core/src/schema.d.ts +44 -44
  122. package/dist/_dts/theme-core/src/site-styles/backgroundAppearance.d.ts +2 -2
  123. package/dist/_dts/theme-core/src/site-styles/designState.d.ts +16 -16
  124. package/dist/_dts/theme-core/src/site-styles/pageDesignIntent.d.ts +4 -4
  125. package/dist/_dts/theme-core/src/site-styles/tokenRecipes.d.ts +1 -1
  126. package/dist/_dts/theme-core/src/tokens/themeColorTokens.d.ts +1 -1
  127. package/dist/{chunk-AQ6RT6LZ.mjs → chunk-4TLTV7OB.mjs} +2 -2
  128. package/dist/{chunk-NT76QGIJ.mjs → chunk-7ED5UNHL.mjs} +14 -0
  129. package/dist/{chunk-XIK2HYI3.mjs → chunk-EL55VV6Q.mjs} +49 -9
  130. package/dist/{chunk-A63UXN3P.mjs → chunk-VHIMXQ7Z.mjs} +106 -4
  131. package/dist/{chunk-AJZGT6YG.mjs → chunk-VTPPVAOL.mjs} +1 -1
  132. package/dist/{chunk-PCNCETBI.mjs → chunk-XMATDBXO.mjs} +2082 -408
  133. package/dist/cli/index.mjs +2809 -787
  134. package/dist/cli/init-docs/content/cli-reference.md +43 -4
  135. package/dist/cli/init-docs/content/content-management.md +22 -0
  136. package/dist/client/{accordion-RNQIPG55.mjs → accordion-GUNXIIZR.mjs} +1 -1
  137. package/dist/client/{bookingForm-HYEEVRLP.mjs → bookingForm-AOU4VHP5.mjs} +3 -3
  138. package/dist/client/bookings.mjs +211 -105
  139. package/dist/client/{carousel-W7DQ3YPU.mjs → carousel-4QSX2JPC.mjs} +1 -1
  140. package/dist/client/{cart-PXJ3SXXO.mjs → cart-JKN6C4ZB.mjs} +4 -4
  141. package/dist/client/{checkout-X3JYSOXI.mjs → checkout-YY6YMITW.mjs} +4 -4
  142. package/dist/client/{chunk-PICR3NTQ.mjs → chunk-6H27K6AA.mjs} +1 -1
  143. package/dist/client/{chunk-J6TMMMXW.mjs → chunk-A3YN7M3G.mjs} +1 -1
  144. package/dist/client/{chunk-YYVGSYBO.mjs → chunk-BFCJZODD.mjs} +3 -3
  145. package/dist/client/{chunk-IXDSXF2D.mjs → chunk-CPKMJVBD.mjs} +353 -187
  146. package/dist/client/{chunk-6SYGVVWA.mjs → chunk-GWCRCJLB.mjs} +9 -0
  147. package/dist/client/{chunk-Q47XGAGS.mjs → chunk-H7A6PUGM.mjs} +1 -1
  148. package/dist/client/{chunk-WERRJXNJ.mjs → chunk-SY543WZ3.mjs} +1 -1
  149. package/dist/client/{chunk-DUOHLWLF.mjs → chunk-VJROUYOQ.mjs} +2 -2
  150. package/dist/client/client.mjs +4495 -3855
  151. package/dist/client/{courseRegistration-QZ67PVHO.mjs → courseRegistration-UU2KMPSQ.mjs} +3 -3
  152. package/dist/client/{eventCalendar-V3BDHQNT.mjs → eventCalendar-WIVNS2TM.mjs} +6 -6
  153. package/dist/client/{eventCombined-XYFRN2RG.mjs → eventCombined-P3G5MW4J.mjs} +6 -6
  154. package/dist/client/{eventDetails-TFE5TWKV.mjs → eventDetails-2VHJ2RDG.mjs} +6 -6
  155. package/dist/client/{eventListing-IQ6AGWZ6.mjs → eventListing-TGO6JI3R.mjs} +6 -6
  156. package/dist/client/{eventRegistration-WGJVFKQJ.mjs → eventRegistration-B46HEJES.mjs} +6 -6
  157. package/dist/client/{form-6UZY6RUA.mjs → form-VKRTFUNQ.mjs} +1 -1
  158. package/dist/client/{headerSection-RSFXY6V3.mjs → headerSection-XUX4ZJLO.mjs} +2 -2
  159. package/dist/client/hooks.mjs +34 -3
  160. package/dist/client/{locationMap-24FH67I4.mjs → locationMap-NQFI5MJO.mjs} +2 -2
  161. package/dist/client/{newsletterForm-QVL3MLTN.mjs → newsletterForm-P2GETCQK.mjs} +1 -1
  162. package/dist/client/{productDetail-ZDYN3G4T.mjs → productDetail-7NQ7O5IF.mjs} +4 -4
  163. package/dist/client/{productList-4BMJLQHZ.mjs → productList-RX6ZJ7BL.mjs} +4 -4
  164. package/dist/client/rendering/client.mjs +4170 -3768
  165. package/dist/client/rendering/islands.mjs +18 -18
  166. package/dist/client/rendering.mjs +1700 -1065
  167. package/dist/client/{shop-U5C634FF.mjs → shop-BHN5AKY6.mjs} +4 -4
  168. package/dist/preview-next/before-render.mjs +14 -0
  169. package/dist/preview-next/client/runtime.mjs +5 -5
  170. package/dist/preview-next/middleware.mjs +14 -0
  171. package/dist/{sdk-runtime-VGV46WVB.mjs → sdk-runtime-X2LXQ5IC.mjs} +6 -6
  172. package/dist/server/components.mjs +3255 -2827
  173. package/dist/server/config-template-validation.mjs +29 -20
  174. package/dist/server/config-validation.mjs +36 -4
  175. package/dist/server/config.mjs +36 -4
  176. package/dist/server/data.mjs +34 -3
  177. package/dist/server/index.mjs +44 -1
  178. package/dist/server/next/middleware.mjs +120 -0
  179. package/dist/server/next.mjs +3405 -2859
  180. package/dist/server/page-converter.mjs +20 -3
  181. package/dist/server/prebuild.mjs +1 -1
  182. package/dist/server/rendering/server.mjs +3264 -2837
  183. package/dist/server/rendering.mjs +3295 -2844
  184. package/dist/server/routing.mjs +35 -4
  185. package/dist/server/server.mjs +2358 -2188
  186. package/dist/server/theme-bridge.mjs +140 -0
  187. package/dist/styles/index.css +139 -0
  188. package/package.json +5 -1
  189. package/dist/SdkPreviewModeRuntime-NHTLRNQD.mjs +0 -8
@@ -1,15 +1,15 @@
1
1
  "use client";
2
- import { LeafletStaticMap } from './chunk-WERRJXNJ.mjs';
3
- import { resolveCoursePricingState, useWaitlistClaim, MagicLinkForm, formatCoursePriceAmount, formatCoursePriceDisplay } from './chunk-PICR3NTQ.mjs';
2
+ import { LeafletStaticMap } from './chunk-SY543WZ3.mjs';
3
+ import { resolveCoursePricingState, useWaitlistClaim, MagicLinkForm, formatCoursePriceAmount, formatCoursePriceDisplay } from './chunk-6H27K6AA.mjs';
4
4
  import { CreditCard, Ticket, Star, Sparkles } from './chunk-6N7YRNYU.mjs';
5
- import { usePolling, buildBookingPaymentSelectionViewModel } from './chunk-DUOHLWLF.mjs';
5
+ import { usePolling, buildBookingPaymentSelectionViewModel } from './chunk-VJROUYOQ.mjs';
6
6
  import { SpinnerNode } from './chunk-OLAOAZEO.mjs';
7
- import { createBodyThemePortalRoot, TabBarFragment, useDiscountCodeUi, logoutPortal, consumeSearchParam, StepIndicator, DiscountCodeDisclosure, TerminalPanel, StateIcon, formatPrice, CrossIcon, ClockIcon, LoggedInAsUser, CheckIcon } from './chunk-J6TMMMXW.mjs';
7
+ import { createBodyThemePortalRoot, TabBarFragment, useDiscountCodeUi, logoutPortal, consumeSearchParam, StepIndicator, DiscountCodeDisclosure, TerminalPanel, StateIcon, formatPrice, CrossIcon, ClockIcon, LoggedInAsUser, CheckIcon } from './chunk-A3YN7M3G.mjs';
8
8
  import { getFieldValidationControlProps, FieldValidationMessage } from './chunk-3DCJ45Z2.mjs';
9
9
  import { ModalShell } from './chunk-WBQG7VJA.mjs';
10
10
  import { isSpamProtectionEnabled, AltchaWidget, getChallengeUrl } from './chunk-LJCX6V7Q.mjs';
11
11
  import { useBlockApi, FeedbackMessage } from './chunk-TNGL2WJF.mjs';
12
- import { buildEventRuntimeFilters, buildEventCalendarListingRender, groupEventsByDate, getOrderedDayNames, formatDateKey, getEventRuntimeFiltersKey, themeButtonClassName, normalizeCsvIds, normalizeOptionalId, normalizeEventBlockKind, normalizeCsvString, EventCompactRow, EmptyState, getContainerClass, getCardOrientation, isToday, MAX_EVENTS_PER_DAY, formatTime, WIZARD_STEPS, getWizardStepsForContext, getStepNumber, formatEventPriceDisplay, resolveRuntimeEventPricing, formatEventPrice, formatEventDateParts, resolveEventLinkProps, DateTimeTile, assertNever, EventCard, buildCalendarViewport, shiftCalendarAnchorDate, addMonths, getThreeMonthRange, buildTimetableModel, formatDate, MONTH_NAMES, getEventAvailability, formatEventTeacherLine, toRegistrationEvents, isEventContentType, findPreSelectedEvent, resolveRegistrationTarget, findEventByOccurrenceId, resolveEventRequiresPayment, resolveDiscountContext } from './chunk-6SYGVVWA.mjs';
12
+ import { buildEventRuntimeFilters, buildEventCalendarListingRender, groupEventsByDate, getOrderedDayNames, formatDateKey, getEventRuntimeFiltersKey, themeButtonClassName, normalizeCsvIds, normalizeOptionalId, normalizeEventBlockKind, normalizeCsvString, EventCompactRow, EmptyState, getContainerClass, getCardOrientation, isToday, MAX_EVENTS_PER_DAY, formatTime, WIZARD_STEPS, getWizardStepsForContext, getStepNumber, formatEventPriceDisplay, resolveRuntimeEventPricing, formatEventPrice, formatEventDateParts, resolveEventLinkProps, DateTimeTile, assertNever, EventCard, buildCalendarViewport, shiftCalendarAnchorDate, addMonths, getThreeMonthRange, buildTimetableModel, formatDate, MONTH_NAMES, getEventAvailability, formatEventTeacherLine, toRegistrationEvents, isEventContentType, findPreSelectedEvent, resolveRegistrationTarget, findEventByOccurrenceId, resolveEventRequiresPayment, resolveDiscountContext } from './chunk-GWCRCJLB.mjs';
13
13
  import { publicEventPricingType, getBookingLocalDateKey, toScheduledBookingDateTime, toBookingTimeZone, publicEventPricingCurrency, formatBookingDateTime, publicEventPricingMode, publicEventScalarPrice, publicEventTicketTypes, formatCurrencyAmount, formatBookingDate, formatBookingTime } from './chunk-PCHACQEB.mjs';
14
14
  import React9, { createContext, useId, useMemo, useReducer, useState, useRef, useEffect, useCallback, useContext } from 'react';
15
15
  import { createPortal } from 'react-dom';
@@ -337,6 +337,7 @@ function transformEligibilityResponse(response) {
337
337
  requiresPayment: eligibility.requiresPayment,
338
338
  pricingMode: eligibility.pricingMode,
339
339
  selectedTicketTypeId: eligibility.selectedTicketTypeId ?? null,
340
+ ticketLines: eligibility.ticketLines ?? [],
340
341
  ticketTypes: eligibility.ticketTypes ?? [],
341
342
  isFull: eligibility.isFull,
342
343
  waitlistEnabled: eligibility.waitlistEnabled,
@@ -393,6 +394,72 @@ function findSelectedTicketType(input) {
393
394
  }).find((ticketType) => ticketType.id === input.selectedTicketTypeId) ?? null;
394
395
  }
395
396
 
397
+ // ../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts
398
+ function normalizeTicketSelectionLines(lines) {
399
+ const quantitiesByTicketType = /* @__PURE__ */ new Map();
400
+ for (const line of lines) {
401
+ const quantity = Math.floor(line.quantity);
402
+ if (quantity <= 0) continue;
403
+ const key = line.ticketTypeId ?? null;
404
+ quantitiesByTicketType.set(key, (quantitiesByTicketType.get(key) ?? 0) + quantity);
405
+ }
406
+ return [...quantitiesByTicketType.entries()].map(([ticketTypeId, quantity]) => ({
407
+ ticketTypeId,
408
+ quantity
409
+ }));
410
+ }
411
+ function getTicketSelectionTotal(lines) {
412
+ return normalizeTicketSelectionLines(lines).reduce((total, line) => total + line.quantity, 0);
413
+ }
414
+ function getTicketLineQuantity(lines, ticketTypeId) {
415
+ return normalizeTicketSelectionLines(lines).find((line) => line.ticketTypeId === ticketTypeId)?.quantity ?? 0;
416
+ }
417
+ function getSingleSelectedTicketTypeId(lines) {
418
+ const normalized = normalizeTicketSelectionLines(lines);
419
+ return normalized.length === 1 ? normalized[0]?.ticketTypeId ?? null : null;
420
+ }
421
+ function setTicketLineQuantity(input) {
422
+ const nextQuantity = Math.max(0, Math.floor(input.quantity));
423
+ const normalized = normalizeTicketSelectionLines(input.lines).filter((line) => line.ticketTypeId !== input.ticketTypeId);
424
+ if (nextQuantity <= 0) {
425
+ return normalized;
426
+ }
427
+ return [
428
+ ...normalized,
429
+ {
430
+ ticketTypeId: input.ticketTypeId,
431
+ quantity: nextQuantity
432
+ }
433
+ ];
434
+ }
435
+ function getTicketTypeLineMax(input) {
436
+ const otherSelectedQuantity = input.totalSelectedQuantity - input.currentQuantity;
437
+ const maxByBlock = input.maxTickets - otherSelectedQuantity;
438
+ const maxByEvent = input.availableSpots == null ? input.maxTickets : input.availableSpots - otherSelectedQuantity;
439
+ const maxByTicketType = input.ticketTypeRemainingCapacity ?? input.maxTickets;
440
+ return Math.max(0, Math.min(maxByBlock, maxByEvent, maxByTicketType));
441
+ }
442
+ function calculateTicketSelectionSubtotal(input) {
443
+ return normalizeTicketSelectionLines(input.lines).reduce((subtotal, line) => {
444
+ const ticketType = line.ticketTypeId ? input.ticketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null : null;
445
+ const unitPriceCents = ticketType?.priceAmount ?? input.fallbackUnitPriceCents;
446
+ return subtotal + Math.max(0, unitPriceCents) * line.quantity;
447
+ }, 0);
448
+ }
449
+ function buildTicketSelectionSummaryLines(input) {
450
+ return normalizeTicketSelectionLines(input.lines).map((line) => {
451
+ const ticketType = line.ticketTypeId ? input.ticketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null : null;
452
+ const unitPriceCents = ticketType?.priceAmount ?? input.fallbackUnitPriceCents;
453
+ return {
454
+ ticketTypeId: line.ticketTypeId,
455
+ label: ticketType?.name ?? input.fallbackLabel,
456
+ quantity: line.quantity,
457
+ unitPriceCents,
458
+ subtotalCents: Math.max(0, unitPriceCents) * line.quantity
459
+ };
460
+ });
461
+ }
462
+
396
463
  // ../blocks/src/system/runtime/nodes/event-registration/hooks.state.ts
397
464
  var initialEligibilityLookupState = {
398
465
  tag: "idle",
@@ -499,6 +566,7 @@ function usePortalAuth() {
499
566
  }
500
567
  function useEligibility(params) {
501
568
  const api = useBlockApi();
569
+ const ticketSelectionsKey = JSON.stringify(normalizeTicketSelectionLines(params.ticketSelections));
502
570
  const [state, dispatch] = useReducer(
503
571
  eligibilityLookupReducer,
504
572
  initialEligibilityLookupState
@@ -512,7 +580,8 @@ function useEligibility(params) {
512
580
  dispatch({ type: "reset" });
513
581
  return;
514
582
  }
515
- if (!params.occurrenceId || !params.email.trim()) {
583
+ const ticketSelections = JSON.parse(ticketSelectionsKey);
584
+ if (!params.occurrenceId || !params.email.trim() || ticketSelections.length === 0) {
516
585
  requestIdRef.current += 1;
517
586
  dispatch({ type: "reset" });
518
587
  return;
@@ -524,10 +593,9 @@ function useEligibility(params) {
524
593
  const result = await api.checkEligibility({
525
594
  occurrenceId: params.occurrenceId,
526
595
  attendanceMode: params.attendanceMode ?? void 0,
527
- ticketTypeId: params.ticketTypeId ?? void 0,
596
+ ticketSelections,
528
597
  selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
529
- email: params.email.trim().toLowerCase(),
530
- ticketsCount: params.ticketsCount
598
+ email: params.email.trim().toLowerCase()
531
599
  });
532
600
  if (cancelled || requestIdRef.current !== requestId) return;
533
601
  dispatch({
@@ -546,7 +614,15 @@ function useEligibility(params) {
546
614
  return () => {
547
615
  cancelled = true;
548
616
  };
549
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
617
+ }, [
618
+ api,
619
+ params.enabled,
620
+ params.occurrenceId,
621
+ params.attendanceMode,
622
+ ticketSelectionsKey,
623
+ params.selectedSlidingAmount,
624
+ params.email
625
+ ]);
550
626
  return {
551
627
  state,
552
628
  eligibility: state.eligibility,
@@ -622,9 +698,15 @@ function savePendingRegistration(siteId, occurrenceId, data) {
622
698
  }
623
699
  try {
624
700
  const key = getStorageKey(siteId, occurrenceId);
701
+ const ticketSelections = normalizeTicketSelectionLines(data.ticketSelections);
702
+ if (ticketSelections.length === 0) {
703
+ console.warn("[sessionStorage] No ticket selections to save, skipping registration state");
704
+ return;
705
+ }
625
706
  const entry = {
626
707
  ...data,
627
- version: 3,
708
+ ticketSelections,
709
+ version: 4,
628
710
  timestamp: Date.now()
629
711
  };
630
712
  sessionStorage.setItem(key, JSON.stringify(entry));
@@ -670,8 +752,8 @@ function parseAndValidateEntry(stored, key) {
670
752
  return null;
671
753
  }
672
754
  const version = raw.version;
673
- if (version !== 1 && version !== 2 && version !== 3) {
674
- console.warn("[sessionStorage] Unknown version, ignoring entry");
755
+ if (version !== 4) {
756
+ console.warn("[sessionStorage] Outdated version, ignoring entry");
675
757
  sessionStorage.removeItem(key);
676
758
  return null;
677
759
  }
@@ -687,34 +769,12 @@ function parseAndValidateEntry(stored, key) {
687
769
  sessionStorage.removeItem(key);
688
770
  return null;
689
771
  }
690
- if (entry.version === 1) {
691
- const legacy = entry;
692
- return {
693
- version: 3,
694
- attendeeId: legacy.attendeeId,
695
- formData: legacy.formData,
696
- ticketsCount: legacy.ticketsCount,
697
- selectedEventId: legacy.selectedEventId,
698
- selectedAttendanceMode: null,
699
- selectedTicketTypeId: null,
700
- paymentSelection: { method: legacy.paymentMethod },
701
- timestamp: legacy.timestamp
702
- };
703
- }
704
- if (entry.version === 2) {
705
- return {
706
- version: 3,
707
- attendeeId: entry.attendeeId,
708
- formData: entry.formData,
709
- ticketsCount: entry.ticketsCount,
710
- selectedEventId: entry.selectedEventId,
711
- selectedAttendanceMode: null,
712
- selectedTicketTypeId: null,
713
- paymentSelection: entry.paymentSelection,
714
- timestamp: entry.timestamp
715
- };
772
+ const ticketSelections = normalizeTicketSelectionLines(entry.ticketSelections);
773
+ if (ticketSelections.length === 0 || getTicketSelectionTotal(ticketSelections) <= 0) {
774
+ sessionStorage.removeItem(key);
775
+ return null;
716
776
  }
717
- return entry;
777
+ return { ...entry, ticketSelections };
718
778
  } catch {
719
779
  console.warn("[sessionStorage] Failed to parse entry, removing");
720
780
  sessionStorage.removeItem(key);
@@ -746,8 +806,9 @@ function cleanupStaleRegistrations() {
746
806
  const stored = sessionStorage.getItem(key);
747
807
  if (!stored) continue;
748
808
  const entry = JSON.parse(stored);
749
- const age = Date.now() - entry.timestamp;
750
- if (age > STALE_THRESHOLD_MS) {
809
+ const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
810
+ const age = Date.now() - timestamp;
811
+ if (!Number.isFinite(timestamp) || age > STALE_THRESHOLD_MS) {
751
812
  keysToRemove.push(key);
752
813
  }
753
814
  } catch {
@@ -835,14 +896,16 @@ function getEventAttendanceModeLabel(mode) {
835
896
  }
836
897
  }
837
898
  function TicketCounter(props) {
838
- const { ticketsCount, maxAllowed, onTicketsChange } = props;
899
+ const { ticketsCount, maxAllowed, label, onTicketsChange } = props;
900
+ const minAllowed = props.minAllowed ?? 1;
839
901
  return /* @__PURE__ */ jsxs("div", { className: "er-row er-counter", children: [
840
902
  /* @__PURE__ */ jsx(
841
903
  "button",
842
904
  {
843
905
  type: "button",
844
- onClick: () => onTicketsChange(Math.max(1, ticketsCount - 1)),
845
- disabled: ticketsCount <= 1,
906
+ "aria-label": `Decrease ${label}`,
907
+ onClick: () => onTicketsChange(Math.max(minAllowed, ticketsCount - 1)),
908
+ disabled: ticketsCount <= minAllowed,
846
909
  className: "er-counter-btn",
847
910
  children: "-"
848
911
  }
@@ -852,6 +915,7 @@ function TicketCounter(props) {
852
915
  "button",
853
916
  {
854
917
  type: "button",
918
+ "aria-label": `Increase ${label}`,
855
919
  onClick: () => onTicketsChange(Math.min(maxAllowed, ticketsCount + 1)),
856
920
  disabled: ticketsCount >= maxAllowed,
857
921
  className: "er-counter-btn",
@@ -904,14 +968,86 @@ function SlidingScaleAmountField(props) {
904
968
  props.message ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: props.message }) : null
905
969
  ] });
906
970
  }
907
- function getTicketTypeCounterAvailabilityLabel(input) {
908
- if (input.selectedRemainingCapacity !== null) {
909
- return `${input.selectedRemainingCapacity} remaining for this ticket type`;
910
- }
911
- if (input.eventAvailableSpots != null) {
912
- return `${input.eventAvailableSpots} spots available`;
913
- }
914
- return null;
971
+ function TicketTypeQuantityRows(props) {
972
+ const totalSelectedQuantity = getTicketSelectionTotal(props.ticketSelections);
973
+ const selectedTicketTypes = props.ticketTypes.filter(
974
+ (ticketType) => getTicketLineQuantity(props.ticketSelections, ticketType.id) > 0
975
+ );
976
+ return /* @__PURE__ */ jsxs("div", { className: "er-ticket-lines", children: [
977
+ /* @__PURE__ */ jsx("div", { className: "er-ticket-line-list", role: "list", "aria-label": "Tickets", children: props.ticketTypes.map((ticketType) => {
978
+ const quantity = getTicketLineQuantity(props.ticketSelections, ticketType.id);
979
+ const disabled = ticketType.availability.kind === "unavailable";
980
+ const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
981
+ const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, props.currency) : "Free";
982
+ const maxAllowed = disabled ? 0 : getTicketTypeLineMax({
983
+ currentQuantity: quantity,
984
+ totalSelectedQuantity,
985
+ maxTickets: props.maxTickets,
986
+ availableSpots: props.availableSpots,
987
+ ticketTypeRemainingCapacity: ticketType.availability.remainingCapacity
988
+ });
989
+ return /* @__PURE__ */ jsxs(
990
+ "div",
991
+ {
992
+ className: `er-ticket-line${disabled ? " er-ticket-line--disabled" : ""}`,
993
+ role: "listitem",
994
+ children: [
995
+ /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-main", children: [
996
+ /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-heading", children: [
997
+ /* @__PURE__ */ jsx("span", { className: "er-ticket-line-name", children: ticketType.name }),
998
+ /* @__PURE__ */ jsx("span", { className: "er-ticket-line-price", children: priceLabel })
999
+ ] }),
1000
+ ticketType.description ? /* @__PURE__ */ jsx("p", { className: "er-ticket-line-detail", children: ticketType.description }) : null,
1001
+ availabilityLabel ? /* @__PURE__ */ jsx("p", { className: "er-ticket-line-detail", children: availabilityLabel }) : ticketType.availability.remainingCapacity !== null ? /* @__PURE__ */ jsxs("p", { className: "er-ticket-line-detail", children: [
1002
+ ticketType.availability.remainingCapacity,
1003
+ " remaining for this ticket type"
1004
+ ] }) : null
1005
+ ] }),
1006
+ /* @__PURE__ */ jsx(
1007
+ TicketCounter,
1008
+ {
1009
+ ticketsCount: quantity,
1010
+ minAllowed: 0,
1011
+ maxAllowed,
1012
+ label: ticketType.name,
1013
+ onTicketsChange: (nextQuantity) => {
1014
+ props.onTicketSelectionsChange?.(setTicketLineQuantity({
1015
+ lines: props.ticketSelections,
1016
+ ticketTypeId: ticketType.id,
1017
+ quantity: nextQuantity
1018
+ }));
1019
+ }
1020
+ }
1021
+ )
1022
+ ]
1023
+ },
1024
+ ticketType.id
1025
+ );
1026
+ }) }),
1027
+ /* @__PURE__ */ jsx(
1028
+ TicketCounterLimit,
1029
+ {
1030
+ maximumLabel: `${props.maxTickets} tickets maximum`,
1031
+ availabilityLabel: props.availableSpots != null ? `${props.availableSpots} spots available` : null
1032
+ }
1033
+ ),
1034
+ selectedTicketTypes.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-summary", "aria-live": "polite", children: [
1035
+ /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-summary-title", children: [
1036
+ totalSelectedQuantity,
1037
+ " ticket",
1038
+ totalSelectedQuantity === 1 ? "" : "s",
1039
+ " selected"
1040
+ ] }),
1041
+ /* @__PURE__ */ jsx("ul", { className: "er-ticket-line-summary-list", children: selectedTicketTypes.map((ticketType) => {
1042
+ const quantity = getTicketLineQuantity(props.ticketSelections, ticketType.id);
1043
+ return /* @__PURE__ */ jsxs("li", { children: [
1044
+ quantity,
1045
+ " x ",
1046
+ ticketType.name
1047
+ ] }, ticketType.id);
1048
+ }) })
1049
+ ] }) : props.hasAvailableTicketTypes ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "Choose at least one ticket to continue." }) : /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "No ticket types are currently available." })
1050
+ ] });
915
1051
  }
916
1052
  function getAttendanceModeDetail(attendanceMode) {
917
1053
  if (attendanceMode.availableSpots === null) {
@@ -929,6 +1065,7 @@ function AttendanceModeTiles(props) {
929
1065
  "button",
930
1066
  {
931
1067
  type: "button",
1068
+ "aria-pressed": isSelected,
932
1069
  className: `er-payment-tile${isSelected ? " er-payment-tile--selected" : ""}`,
933
1070
  onClick: () => props.onAttendanceModeChange?.(attendanceMode.mode),
934
1071
  children: [
@@ -965,8 +1102,9 @@ var TicketSelectionStep = ({
965
1102
  onAttendanceModeChange,
966
1103
  ticketsCount,
967
1104
  maxTickets,
968
- selectedTicketTypeId = null,
969
- onTicketTypeChange,
1105
+ visibleTicketTypes: visibleTicketTypesOverride,
1106
+ ticketSelections = [],
1107
+ onTicketSelectionsChange,
970
1108
  slidingScale = null,
971
1109
  selectedSlidingAmountInput = "",
972
1110
  slidingScaleSelectionMessage = null,
@@ -984,28 +1122,22 @@ var TicketSelectionStep = ({
984
1122
  const eventPricingMode = eventPricing ? publicEventPricingMode(eventPricing) : void 0;
985
1123
  const eventPricingCurrency = eventPricing ? publicEventPricingCurrency(eventPricing) : null;
986
1124
  const usesTicketTypes = eventPricingMode === "ticket_types" && !canJoinWaitlist;
987
- const visibleTicketTypes = usesTicketTypes ? getVisibleTicketTypes({
1125
+ const visibleTicketTypes = usesTicketTypes ? visibleTicketTypesOverride ?? getVisibleTicketTypes({
988
1126
  event,
989
1127
  eligibility: null,
990
1128
  selectedAttendanceMode
991
1129
  }) : [];
992
- const selectedTicketType = usesTicketTypes ? visibleTicketTypes.find((ticketType) => ticketType.id === selectedTicketTypeId) ?? null : null;
993
1130
  const implicitTicketType = usesTicketTypes ? getImplicitSingleAvailableTicketType(visibleTicketTypes) : null;
994
- const effectiveSelectedTicketType = selectedTicketType ?? implicitTicketType;
995
- const shouldShowTicketTypeTiles = usesTicketTypes && implicitTicketType === null;
996
- const selectedRemainingCapacity = effectiveSelectedTicketType?.availability.remainingCapacity ?? null;
997
- const noSellableTicketTypes = usesTicketTypes && (visibleTicketTypes.length === 0 || visibleTicketTypes.every((ticketType) => ticketType.availability.kind === "unavailable"));
1131
+ const noVisibleTicketTypes = usesTicketTypes && visibleTicketTypes.length === 0;
1132
+ const hasAvailableTicketTypes = usesTicketTypes && visibleTicketTypes.some((ticketType) => ticketType.availability.kind === "available");
1133
+ const aggregateRemainingCapacity = implicitTicketType?.availability.remainingCapacity ?? null;
998
1134
  const maxAllowed = getMaxAllowedTicketCount({
999
1135
  canJoinWaitlist,
1000
1136
  usesTicketTypes,
1001
1137
  maxTickets,
1002
1138
  availableSpots,
1003
- selectedTicketTypeRemainingCapacity: selectedRemainingCapacity,
1004
- hasSelectedTicketType: effectiveSelectedTicketType !== null
1005
- });
1006
- const selectedTicketTypeAvailabilityLabel = getTicketTypeCounterAvailabilityLabel({
1007
- selectedRemainingCapacity,
1008
- eventAvailableSpots: event?.availableSpots
1139
+ selectedTicketTypeRemainingCapacity: aggregateRemainingCapacity,
1140
+ hasSelectedTicketType: implicitTicketType !== null
1009
1141
  });
1010
1142
  const eventAvailabilityLabel = !canJoinWaitlist && event?.availableSpots != null ? `${event.availableSpots} spots available` : null;
1011
1143
  if (deliveryMode === "hybrid" && !selectedAttendanceMode) {
@@ -1022,7 +1154,7 @@ var TicketSelectionStep = ({
1022
1154
  /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "Choose whether you want to attend in person or online." })
1023
1155
  ] });
1024
1156
  }
1025
- if (isSoldOut && !canJoinWaitlist && !usesTicketTypes) {
1157
+ if (isSoldOut && !canJoinWaitlist) {
1026
1158
  return /* @__PURE__ */ jsxs("div", { className: "er-stack", children: [
1027
1159
  /* @__PURE__ */ jsx("h3", { className: "er-title", children: "Sold Out" }),
1028
1160
  /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "Sorry you missed out! This event is fully booked." })
@@ -1040,52 +1172,21 @@ var TicketSelectionStep = ({
1040
1172
  }
1041
1173
  )
1042
1174
  ] }) : null,
1043
- usesTicketTypes ? /* @__PURE__ */ jsxs(Fragment, { children: [
1044
- shouldShowTicketTypeTiles ? /* @__PURE__ */ jsxs(Fragment, { children: [
1045
- /* @__PURE__ */ jsx("h3", { className: "er-title", children: "Choose a ticket type" }),
1046
- /* @__PURE__ */ jsx("div", { className: "er-payment-tiles", role: "list", "aria-label": "Ticket types", children: visibleTicketTypes.map((ticketType) => {
1047
- const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
1048
- const disabled = ticketType.availability.kind === "unavailable";
1049
- const isSelected = selectedTicketTypeId === ticketType.id;
1050
- const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY) : "Free";
1051
- return /* @__PURE__ */ jsx("div", { role: "listitem", children: /* @__PURE__ */ jsxs(
1052
- "button",
1053
- {
1054
- type: "button",
1055
- className: `er-payment-tile${isSelected && !disabled ? " er-payment-tile--selected" : ""}${disabled ? " er-payment-tile--disabled" : ""}`,
1056
- disabled,
1057
- onClick: () => onTicketTypeChange?.(ticketType.id),
1058
- children: [
1059
- /* @__PURE__ */ jsx("div", { className: "er-payment-tile-label", children: ticketType.name }),
1060
- /* @__PURE__ */ jsx("div", { className: "er-payment-tile-detail", children: priceLabel }),
1061
- ticketType.description ? /* @__PURE__ */ jsx("div", { className: "er-payment-tile-detail", children: ticketType.description }) : null,
1062
- availabilityLabel ? /* @__PURE__ */ jsx("div", { className: "er-payment-tile-detail", children: availabilityLabel }) : null
1063
- ]
1064
- }
1065
- ) }, ticketType.id);
1066
- }) })
1067
- ] }) : null,
1068
- noSellableTicketTypes ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "No ticket types are currently available." }) : !effectiveSelectedTicketType ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "Select one ticket type to continue." }) : effectiveSelectedTicketType.availability.kind === "unavailable" ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: getTicketTypeAvailabilityLabel(effectiveSelectedTicketType) ?? "This ticket type is unavailable." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1069
- /* @__PURE__ */ jsx("h3", { className: "er-title", children: "How many tickets?" }),
1070
- /* @__PURE__ */ jsxs("div", { className: "er-ticket-counter-group", children: [
1071
- /* @__PURE__ */ jsx(
1072
- TicketCounter,
1073
- {
1074
- ticketsCount,
1075
- maxAllowed,
1076
- onTicketsChange
1077
- }
1078
- ),
1079
- /* @__PURE__ */ jsx(
1080
- TicketCounterLimit,
1081
- {
1082
- maximumLabel: `${maxAllowed} tickets maximum`,
1083
- availabilityLabel: selectedTicketTypeAvailabilityLabel
1084
- }
1085
- )
1086
- ] })
1087
- ] })
1088
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1175
+ usesTicketTypes ? /* @__PURE__ */ jsx(Fragment, { children: noVisibleTicketTypes ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "No ticket types are currently available." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1176
+ /* @__PURE__ */ jsx("h3", { className: "er-title", children: "Choose tickets" }),
1177
+ /* @__PURE__ */ jsx(
1178
+ TicketTypeQuantityRows,
1179
+ {
1180
+ ticketTypes: visibleTicketTypes,
1181
+ ticketSelections,
1182
+ maxTickets,
1183
+ availableSpots,
1184
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY,
1185
+ hasAvailableTicketTypes,
1186
+ onTicketSelectionsChange
1187
+ }
1188
+ )
1189
+ ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1089
1190
  slidingScale ? /* @__PURE__ */ jsx(
1090
1191
  SlidingScaleAmountField,
1091
1192
  {
@@ -1103,6 +1204,7 @@ var TicketSelectionStep = ({
1103
1204
  {
1104
1205
  ticketsCount,
1105
1206
  maxAllowed,
1207
+ label: canJoinWaitlist ? "waitlist spots" : "tickets",
1106
1208
  onTicketsChange
1107
1209
  }
1108
1210
  ),
@@ -1546,7 +1648,7 @@ var PaymentSelectionStep = ({
1546
1648
  }) => {
1547
1649
  const occurrenceId = occurrenceContext?.id ?? event?.registrationOccurrenceId;
1548
1650
  const selectedMethod = selectedSelection.method;
1549
- const totalPrice = eligibility?.price ? eligibility.price * ticketsCount : 0;
1651
+ const totalPrice = eligibility?.price ?? 0;
1550
1652
  const canShowDirectPaymentOptions = Boolean(
1551
1653
  eligibility?.eligible && eligibility.canPayDirectly && eligibility.price
1552
1654
  );
@@ -1946,8 +2048,9 @@ function EventRegistrationWizardContent(props) {
1946
2048
  onAttendanceModeChange: wizard.setSelectedAttendanceMode,
1947
2049
  ticketsCount: wizard.ticketsCount,
1948
2050
  maxTickets: wizard.maxTicketsNum,
1949
- selectedTicketTypeId: wizard.selectedTicketTypeId,
1950
- onTicketTypeChange: wizard.setSelectedTicketTypeId,
2051
+ visibleTicketTypes: wizard.eligibility ? wizard.visibleTicketTypes : void 0,
2052
+ ticketSelections: wizard.selectedTicketLines,
2053
+ onTicketSelectionsChange: wizard.setSelectedTicketLines,
1951
2054
  slidingScale: wizard.selectedEventSlidingScale,
1952
2055
  selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
1953
2056
  slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
@@ -2055,9 +2158,9 @@ function EventRegistrationWizardActions(props) {
2055
2158
  const isWaitingForEligibility = wizard.portalAuthState === "authenticated" && wizard.eligibilityLoading;
2056
2159
  const isCheckingExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistrationLoading;
2057
2160
  const hasExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistration !== null;
2058
- const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidSelectedTicketType;
2161
+ const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidTicketSelection;
2059
2162
  const isContinueDisabled = isWaitingForEligibility || isCheckingExistingRegistration || hasExistingRegistration || isSoldOutWithoutWaitlist || ticketTypeSelectionBlocked;
2060
- const continueLabel = isCheckingExistingRegistration ? "Checking registration..." : hasExistingRegistration ? "Already registered" : isSoldOutWithoutWaitlist ? "Sold out" : ticketTypeSelectionBlocked ? "Choose a ticket type" : "Continue";
2163
+ const continueLabel = isCheckingExistingRegistration ? "Checking registration..." : hasExistingRegistration ? "Already registered" : isSoldOutWithoutWaitlist ? "Sold out" : ticketTypeSelectionBlocked ? "Choose a ticket" : "Continue";
2061
2164
  return /* @__PURE__ */ jsxs("div", { className: "er-actions", children: [
2062
2165
  !wizard.hasPreSelectedContext && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => wizard.setStep("select-event"), className: "er-back-btn", children: "\u2190 Back" }),
2063
2166
  /* @__PURE__ */ jsx(
@@ -2145,13 +2248,17 @@ function EventRegistrationSummaryBar(props) {
2145
2248
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
2146
2249
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
2147
2250
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
2148
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
2149
2251
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY;
2150
2252
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
2151
- const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
2253
+ const fallbackSubtotalCents = Math.max(
2254
+ 0,
2255
+ ((summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0) * wizard.ticketsCount
2256
+ );
2257
+ const subtotalCents = Math.max(0, wizard.ticketSelectionSubtotalCents ?? fallbackSubtotalCents);
2152
2258
  const autoAppliedDiscount = wizard.discountCode.trim().length > 0 ? null : wizard.autoAppliedDiscount;
2153
2259
  const autoAppliedDiscountCents = Math.max(0, autoAppliedDiscount?.computedAmountCents ?? 0);
2154
2260
  const discountedTotalCents = Math.max(0, subtotalCents - autoAppliedDiscountCents);
2261
+ const selectedLineSummary = (wizard.ticketSelectionSummaryLines ?? []).filter((line) => line.ticketTypeId !== null).map((line) => `${line.quantity} x ${line.label}`).join(", ");
2155
2262
  const selectedDirectPaymentOption = getSelectedDirectPaymentOption({
2156
2263
  wizard,
2157
2264
  totalCents: discountedTotalCents,
@@ -2188,9 +2295,9 @@ function EventRegistrationSummaryBar(props) {
2188
2295
  /* @__PURE__ */ jsx("h4", { className: "er-summary-bar-event", children: summaryTitle }),
2189
2296
  /* @__PURE__ */ jsxs("span", { className: "er-summary-bar-meta", children: [
2190
2297
  summaryDate && formatEventDateTime(summaryDate, { timeZone: summaryTimeZone }),
2191
- wizard.selectedTicketType ? /* @__PURE__ */ jsxs(Fragment, { children: [
2298
+ selectedLineSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
2192
2299
  " \xB7 ",
2193
- wizard.selectedTicketType.name
2300
+ selectedLineSummary
2194
2301
  ] }) : null,
2195
2302
  wizard.step !== "select-event" && wizard.step !== "select-tickets" && wizard.ticketsCount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2196
2303
  " \xB7 ",
@@ -2204,8 +2311,8 @@ function EventRegistrationSummaryBar(props) {
2204
2311
  teamMemberNames.join(", ")
2205
2312
  ] })
2206
2313
  ] }),
2207
- summaryPrice > 0 && /* @__PURE__ */ jsx("div", { className: "er-summary-bar-pricing", children: productMethod ? /* @__PURE__ */ jsxs(Fragment, { children: [
2208
- /* @__PURE__ */ jsx("span", { className: "er-summary-bar-price er-summary-bar-price--struck", children: formatPrice(summaryPrice * wizard.ticketsCount, summaryCurrency) }),
2314
+ subtotalCents > 0 && /* @__PURE__ */ jsx("div", { className: "er-summary-bar-pricing", children: productMethod ? /* @__PURE__ */ jsxs(Fragment, { children: [
2315
+ /* @__PURE__ */ jsx("span", { className: "er-summary-bar-price er-summary-bar-price--struck", children: formatPrice(subtotalCents, summaryCurrency) }),
2209
2316
  /* @__PURE__ */ jsx("div", { className: "er-summary-bar-included", children: wizard.paymentSelection.method === "pass" || wizard.paymentSelection.method === "pass_bundle" ? "1 pass credit" : "Included in membership" })
2210
2317
  ] }) : flexibleBalanceSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
2211
2318
  flexibleBalanceSummary.total ? /* @__PURE__ */ jsxs("div", { className: "er-summary-bar-product-detail", children: [
@@ -2295,7 +2402,7 @@ function useEventRegistrationStripeReturn(options) {
2295
2402
  setRegistrationResult,
2296
2403
  setSelectedEvent,
2297
2404
  setSelectedAttendanceMode,
2298
- setSelectedTicketTypeId,
2405
+ setTicketSelections,
2299
2406
  setStep,
2300
2407
  setTicketsCount,
2301
2408
  step
@@ -2314,13 +2421,13 @@ function useEventRegistrationStripeReturn(options) {
2314
2421
  const pending = getPendingRegistration(siteId, occurrenceId || void 0);
2315
2422
  if (!pending) return;
2316
2423
  setFormData(pending.formData);
2317
- setTicketsCount(pending.ticketsCount);
2424
+ setTicketSelections(pending.ticketSelections);
2425
+ setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
2318
2426
  if (pending.selectedEventId) {
2319
2427
  const event = events.find((e) => e.publicEventId === pending.selectedEventId) ?? null;
2320
2428
  if (event) setSelectedEvent(event);
2321
2429
  }
2322
2430
  setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
2323
- setSelectedTicketTypeId(pending.selectedTicketTypeId ?? null);
2324
2431
  setPaymentSelection(pending.paymentSelection);
2325
2432
  if (registrationStatus === "cancelled") {
2326
2433
  setStep("select-payment");
@@ -2372,6 +2479,10 @@ function useEventRegistrationSubmit(options) {
2372
2479
  }
2373
2480
  options.setStep("submitting");
2374
2481
  try {
2482
+ const ticketSelections = normalizeTicketSelectionLines(options.ticketSelections);
2483
+ if (ticketSelections.length === 0) {
2484
+ throw new Error("Choose at least one ticket to continue.");
2485
+ }
2375
2486
  const isBundle = method === "pass_bundle" || method === "membership_bundle";
2376
2487
  const bundleProductId = isBundle ? passId || membershipId : void 0;
2377
2488
  const selectionSnapshot = (() => {
@@ -2400,11 +2511,10 @@ function useEventRegistrationSubmit(options) {
2400
2511
  const result = await options.api.registerForEvent({
2401
2512
  occurrenceId: submitOccurrenceId,
2402
2513
  attendanceMode: options.selectedAttendanceMode ?? void 0,
2403
- ticketTypeId: options.selectedTicketTypeId ?? void 0,
2514
+ ticketSelections,
2404
2515
  selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
2405
2516
  email: options.formData.email.trim().toLowerCase(),
2406
2517
  name: options.formData.name.trim(),
2407
- ticketsCount: options.ticketsCount,
2408
2518
  phone: options.formData.phone.trim() || void 0,
2409
2519
  notes: options.formData.notes.trim() || void 0,
2410
2520
  altchaSolution: options.altchaSolution,
@@ -2449,10 +2559,9 @@ function useEventRegistrationSubmit(options) {
2449
2559
  savePendingRegistration(siteId, submitOccurrenceId, {
2450
2560
  attendeeId: result.attendeeId,
2451
2561
  formData: options.formData,
2452
- ticketsCount: options.ticketsCount,
2562
+ ticketSelections,
2453
2563
  selectedEventId: options.registrationTarget?.selectedEventId ?? null,
2454
2564
  selectedAttendanceMode: options.selectedAttendanceMode,
2455
- selectedTicketTypeId: options.selectedTicketTypeId ?? null,
2456
2565
  paymentSelection: selectionSnapshot
2457
2566
  });
2458
2567
  }
@@ -2564,7 +2673,6 @@ function useEventRegistrationWizard(options) {
2564
2673
  createInitialRegistrationFlowState
2565
2674
  );
2566
2675
  const step = flowState.step;
2567
- const ticketsCount = flowState.ticketsCount;
2568
2676
  const paymentSelection = flowState.paymentSelection;
2569
2677
  const [selectedEvent, setSelectedEvent] = useState(preSelectedEvent ?? null);
2570
2678
  const [formData, setFormData] = useState({ name: "", email: "", phone: "", notes: "" });
@@ -2575,7 +2683,7 @@ function useEventRegistrationWizard(options) {
2575
2683
  const [selectedAttendanceMode, setSelectedAttendanceMode] = useState(
2576
2684
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
2577
2685
  );
2578
- const [selectedTicketTypeId, setSelectedTicketTypeId] = useState(null);
2686
+ const [selectedTicketLines, setSelectedTicketLines] = useState([]);
2579
2687
  const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState(() => {
2580
2688
  const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
2581
2689
  return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
@@ -2611,14 +2719,51 @@ function useEventRegistrationWizard(options) {
2611
2719
  [options.occurrenceContext, selectedEvent]
2612
2720
  );
2613
2721
  const occurrenceId = registrationTarget?.occurrenceId ?? "";
2722
+ const currentEvent = selectedEvent ?? preSelectedEvent;
2723
+ const currentEventPricing = currentEvent ? resolveRuntimeEventPricing(currentEvent) : null;
2724
+ const currentEventUsesTicketTypes = currentEventPricing !== null && publicEventPricingMode(currentEventPricing) === "ticket_types";
2725
+ const selectedModeAvailability = selectedAttendanceMode ? (currentEvent?.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
2726
+ const currentAvailableSpots = selectedModeAvailability?.availableSpots ?? currentEvent?.availableSpots;
2727
+ const currentWaitlistEnabled = selectedModeAvailability?.waitlistEnabled ?? currentEvent?.waitlistEnabled ?? false;
2728
+ const currentCanJoinWaitlist = currentAvailableSpots === 0 && currentWaitlistEnabled === true;
2729
+ const usesTicketTypeSelections = currentEventUsesTicketTypes && !currentCanJoinWaitlist;
2730
+ const eventTicketTypesForSelection = useMemo(
2731
+ () => getVisibleTicketTypes({
2732
+ event: currentEvent,
2733
+ eligibility: null,
2734
+ selectedAttendanceMode
2735
+ }),
2736
+ [currentEvent, selectedAttendanceMode]
2737
+ );
2738
+ const normalizedSelectedTicketLines = useMemo(
2739
+ () => normalizeTicketSelectionLines(selectedTicketLines),
2740
+ [selectedTicketLines]
2741
+ );
2742
+ const ticketTypeSelectionTotal = useMemo(
2743
+ () => getTicketSelectionTotal(normalizedSelectedTicketLines),
2744
+ [normalizedSelectedTicketLines]
2745
+ );
2746
+ const ticketsCount = usesTicketTypeSelections ? ticketTypeSelectionTotal : flowState.ticketsCount;
2747
+ const ticketSelections = useMemo(() => {
2748
+ if (waitlistClaim.claim) {
2749
+ return [{ ticketTypeId: null, quantity: 1 }];
2750
+ }
2751
+ if (usesTicketTypeSelections) {
2752
+ return normalizedSelectedTicketLines;
2753
+ }
2754
+ return [{ ticketTypeId: null, quantity: ticketsCount }];
2755
+ }, [normalizedSelectedTicketLines, ticketsCount, usesTicketTypeSelections, waitlistClaim.claim]);
2756
+ useEffect(() => {
2757
+ if (flowState.ticketsCount === ticketsCount) return;
2758
+ dispatchFlow({ type: "set-tickets-count", ticketsCount });
2759
+ }, [flowState.ticketsCount, ticketsCount]);
2614
2760
  const eligibilityResult = useEligibility({
2615
2761
  enabled: portalAuthState === "authenticated" || step === "enter-details" || step === "select-payment",
2616
2762
  occurrenceId,
2617
2763
  attendanceMode: selectedAttendanceMode,
2618
- ticketTypeId: selectedTicketTypeId,
2764
+ ticketSelections,
2619
2765
  selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
2620
- email: formData.email,
2621
- ticketsCount
2766
+ email: formData.email
2622
2767
  });
2623
2768
  const existingRegistrationResult = useExistingEventRegistration({
2624
2769
  isAuthenticated: portalAuthState === "authenticated",
@@ -2724,7 +2869,7 @@ function useEventRegistrationWizard(options) {
2724
2869
  setTicketsCount,
2725
2870
  setSelectedEvent,
2726
2871
  setSelectedAttendanceMode,
2727
- setSelectedTicketTypeId,
2872
+ setTicketSelections: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
2728
2873
  setPaymentSelection: (selection) => dispatchFlow({ type: "set-manual-payment-selection", selection }),
2729
2874
  setPendingAttendeeId,
2730
2875
  setStep,
@@ -2737,20 +2882,21 @@ function useEventRegistrationWizard(options) {
2737
2882
  }, [selectedEvent, preSelectedEvent]);
2738
2883
  const visibleTicketTypes = useMemo(
2739
2884
  () => getVisibleTicketTypes({
2740
- event: selectedEvent ?? preSelectedEvent,
2885
+ event: currentEvent,
2741
2886
  eligibility,
2742
2887
  selectedAttendanceMode
2743
2888
  }),
2744
- [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility]
2889
+ [currentEvent, selectedAttendanceMode, eligibility]
2745
2890
  );
2891
+ const singleSelectedTicketTypeId = getSingleSelectedTicketTypeId(normalizedSelectedTicketLines);
2746
2892
  const selectedTicketType = useMemo(
2747
2893
  () => findSelectedTicketType({
2748
- event: selectedEvent ?? preSelectedEvent,
2894
+ event: currentEvent,
2749
2895
  eligibility,
2750
- selectedTicketTypeId,
2896
+ selectedTicketTypeId: singleSelectedTicketTypeId,
2751
2897
  selectedAttendanceMode
2752
2898
  }),
2753
- [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
2899
+ [currentEvent, eligibility, selectedAttendanceMode, singleSelectedTicketTypeId]
2754
2900
  );
2755
2901
  const selectedSlidingAmount = useMemo(
2756
2902
  () => parseCurrencyInputToCents(selectedSlidingAmountInput),
@@ -2765,37 +2911,44 @@ function useEventRegistrationWizard(options) {
2765
2911
  selectedAmount: selectedSlidingAmount
2766
2912
  });
2767
2913
  const ticketTypeSelectionRequired = useMemo(() => {
2768
- const event = selectedEvent ?? preSelectedEvent;
2914
+ const event = currentEvent;
2769
2915
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
2770
2916
  return false;
2771
2917
  }
2772
2918
  if ((event.deliveryMode ?? "in_person") === "hybrid" && !selectedAttendanceMode) {
2773
2919
  return false;
2774
2920
  }
2775
- const selectedModeAvailability = selectedAttendanceMode ? (event.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
2776
- const canJoinWaitlist = (selectedModeAvailability?.availableSpots ?? event.availableSpots) === 0 && (selectedModeAvailability?.waitlistEnabled ?? event.waitlistEnabled) === true;
2921
+ const selectedModeAvailability2 = selectedAttendanceMode ? (event.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
2922
+ const canJoinWaitlist = (selectedModeAvailability2?.availableSpots ?? event.availableSpots) === 0 && (selectedModeAvailability2?.waitlistEnabled ?? event.waitlistEnabled) === true;
2777
2923
  return !canJoinWaitlist;
2778
- }, [selectedAttendanceMode, selectedEvent, preSelectedEvent]);
2779
- const hasValidSelectedTicketType = !ticketTypeSelectionRequired || Boolean(
2780
- selectedTicketType && selectedTicketType.availability.kind === "available"
2781
- );
2924
+ }, [currentEvent, selectedAttendanceMode]);
2925
+ const hasValidTicketSelection = !ticketTypeSelectionRequired || normalizedSelectedTicketLines.length > 0 && normalizedSelectedTicketLines.every((line) => {
2926
+ if (!line.ticketTypeId) return false;
2927
+ const ticketType = visibleTicketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null;
2928
+ return ticketType?.availability.kind === "available";
2929
+ });
2782
2930
  useEffect(() => {
2783
- const event = selectedEvent ?? preSelectedEvent;
2784
- if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
2785
- if (selectedTicketTypeId !== null) {
2786
- setSelectedTicketTypeId(null);
2931
+ if (!usesTicketTypeSelections) {
2932
+ if (selectedTicketLines.length > 0) {
2933
+ setSelectedTicketLines([]);
2787
2934
  }
2788
2935
  return;
2789
2936
  }
2790
- const implicitTicketType = getImplicitSingleAvailableTicketType(visibleTicketTypes);
2791
- if (implicitTicketType && selectedTicketTypeId !== implicitTicketType.id) {
2792
- setSelectedTicketTypeId(implicitTicketType.id);
2937
+ const availableTicketTypes = eventTicketTypesForSelection.filter(
2938
+ (ticketType) => ticketType.availability.kind === "available"
2939
+ );
2940
+ const implicitTicketType = getImplicitSingleAvailableTicketType(eventTicketTypesForSelection);
2941
+ if (implicitTicketType && normalizedSelectedTicketLines.length === 0) {
2942
+ setSelectedTicketLines([{ ticketTypeId: implicitTicketType.id, quantity: 1 }]);
2793
2943
  return;
2794
2944
  }
2795
- if (selectedTicketTypeId && !visibleTicketTypes.some((ticketType) => ticketType.id === selectedTicketTypeId)) {
2796
- setSelectedTicketTypeId(null);
2945
+ const filteredLines = normalizedSelectedTicketLines.filter(
2946
+ (line) => line.ticketTypeId && availableTicketTypes.some((ticketType) => ticketType.id === line.ticketTypeId)
2947
+ );
2948
+ if (filteredLines.length !== normalizedSelectedTicketLines.length) {
2949
+ setSelectedTicketLines(filteredLines);
2797
2950
  }
2798
- }, [selectedEvent, preSelectedEvent, selectedTicketTypeId, visibleTicketTypes]);
2951
+ }, [eventTicketTypesForSelection, normalizedSelectedTicketLines, selectedTicketLines.length, usesTicketTypeSelections]);
2799
2952
  useEffect(() => {
2800
2953
  if (!requireGuestDetails) {
2801
2954
  if (guests.length > 0) setGuests([]);
@@ -2839,21 +2992,20 @@ function useEventRegistrationWizard(options) {
2839
2992
  return resolveEventRequiresPayment({
2840
2993
  eligibility,
2841
2994
  selectedTicketType,
2842
- event: selectedEvent ?? preSelectedEvent
2995
+ event: currentEvent
2843
2996
  });
2844
2997
  },
2845
2998
  [
2846
2999
  eligibility,
2847
3000
  selectedTicketType,
2848
- selectedEvent,
2849
- preSelectedEvent,
3001
+ currentEvent,
2850
3002
  selectedEventSlidingScale,
2851
3003
  selectedSlidingAmount
2852
3004
  ]
2853
3005
  );
2854
3006
  const handleDetailsComplete = async () => {
2855
- if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
2856
- const message = "Select a ticket type to continue.";
3007
+ if (ticketTypeSelectionRequired && !hasValidTicketSelection) {
3008
+ const message = "Choose at least one available ticket to continue.";
2857
3009
  setErrorMessage(message);
2858
3010
  setStep("error");
2859
3011
  return { ok: false, reason: "blocked", message };
@@ -2938,27 +3090,39 @@ function useEventRegistrationWizard(options) {
2938
3090
  const discountContext = useMemo(
2939
3091
  () => resolveDiscountContext({
2940
3092
  paymentSelection,
2941
- event: selectedEvent ?? preSelectedEvent,
3093
+ event: currentEvent,
2942
3094
  eventRequiresPayment,
2943
3095
  ticketsCount
2944
3096
  }),
2945
- [eventRequiresPayment, paymentSelection, selectedEvent, preSelectedEvent, ticketsCount]
3097
+ [currentEvent, eventRequiresPayment, paymentSelection, ticketsCount]
3098
+ );
3099
+ const fallbackUnitPriceCents = useMemo(
3100
+ () => currentEvent ? publicEventScalarPrice(resolveRuntimeEventPricing(currentEvent)) ?? 0 : 0,
3101
+ [currentEvent]
3102
+ );
3103
+ const ticketSelectionSubtotalCents = useMemo(() => {
3104
+ if (!usesTicketTypeSelections) {
3105
+ return Math.max(0, eligibility?.price ?? fallbackUnitPriceCents * ticketsCount);
3106
+ }
3107
+ return calculateTicketSelectionSubtotal({
3108
+ lines: normalizedSelectedTicketLines,
3109
+ ticketTypes: visibleTicketTypes,
3110
+ fallbackUnitPriceCents
3111
+ });
3112
+ }, [eligibility, fallbackUnitPriceCents, normalizedSelectedTicketLines, ticketsCount, usesTicketTypeSelections, visibleTicketTypes]);
3113
+ const ticketSelectionSummaryLines = useMemo(
3114
+ () => buildTicketSelectionSummaryLines({
3115
+ lines: ticketSelections,
3116
+ ticketTypes: visibleTicketTypes,
3117
+ fallbackLabel: "Ticket",
3118
+ fallbackUnitPriceCents
3119
+ }),
3120
+ [fallbackUnitPriceCents, ticketSelections, visibleTicketTypes]
2946
3121
  );
2947
3122
  const discountSubtotalCents = useMemo(() => {
2948
3123
  if (discountContext?.type !== "event") return void 0;
2949
- const event = selectedEvent ?? preSelectedEvent;
2950
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
2951
- return Math.max(0, eventPriceCents * ticketsCount);
2952
- }, [
2953
- discountContext,
2954
- selectedTicketType,
2955
- eligibility,
2956
- selectedEvent,
2957
- preSelectedEvent,
2958
- selectedEventSlidingScale,
2959
- selectedSlidingAmount,
2960
- ticketsCount
2961
- ]);
3124
+ return ticketSelectionSubtotalCents;
3125
+ }, [discountContext, ticketSelectionSubtotalCents]);
2962
3126
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
2963
3127
  const selectionMethod = paymentSelection.method;
2964
3128
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -2966,9 +3130,8 @@ function useEventRegistrationWizard(options) {
2966
3130
  api,
2967
3131
  registrationTarget,
2968
3132
  selectedAttendanceMode,
2969
- selectedTicketTypeId,
3133
+ ticketSelections,
2970
3134
  selectedSlidingAmount,
2971
- ticketsCount,
2972
3135
  formData,
2973
3136
  altchaSolution,
2974
3137
  requireGuestDetails,
@@ -2989,12 +3152,12 @@ function useEventRegistrationWizard(options) {
2989
3152
  setSelectedAttendanceMode(
2990
3153
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
2991
3154
  );
2992
- setSelectedTicketTypeId(null);
3155
+ setSelectedTicketLines([]);
2993
3156
  dispatchFlow({ type: "event-changed", hasPreSelectedContext: true });
2994
3157
  };
2995
3158
  const handleRetry = () => {
2996
3159
  dispatchFlow({ type: "reset", hasPreSelectedContext });
2997
- setSelectedTicketTypeId(null);
3160
+ setSelectedTicketLines([]);
2998
3161
  setSelectedSlidingAmountInput("");
2999
3162
  setSelectedAttendanceMode(
3000
3163
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
@@ -3030,17 +3193,20 @@ function useEventRegistrationWizard(options) {
3030
3193
  setTicketsCount,
3031
3194
  selectedAttendanceMode,
3032
3195
  setSelectedAttendanceMode,
3033
- selectedTicketTypeId,
3034
- setSelectedTicketTypeId,
3196
+ ticketSelections,
3197
+ selectedTicketLines: normalizedSelectedTicketLines,
3198
+ setSelectedTicketLines: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
3035
3199
  selectedSlidingAmountInput,
3036
3200
  setSelectedSlidingAmountInput,
3037
3201
  selectedSlidingAmount,
3038
3202
  selectedEventSlidingScale,
3039
3203
  slidingScaleSelectionMessage,
3040
3204
  selectedTicketType,
3205
+ ticketSelectionSummaryLines,
3206
+ ticketSelectionSubtotalCents,
3041
3207
  visibleTicketTypes,
3042
3208
  ticketTypeSelectionRequired,
3043
- hasValidSelectedTicketType,
3209
+ hasValidTicketSelection,
3044
3210
  eventRequiresPayment,
3045
3211
  formData,
3046
3212
  setFormData,