@riverbankcms/sdk 0.94.0 → 0.95.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 (99) hide show
  1. package/README.md +3 -0
  2. package/dist/{PreviewEditorSidebar-D3PAF7NW.mjs → PreviewEditorSidebar-6FQYKOSB.mjs} +3 -3
  3. package/dist/{PreviewEditorUI-G5JVJEIJ.mjs → PreviewEditorUI-NPO2XMFB.mjs} +3 -3
  4. package/dist/SdkPreviewModeRuntime-PBEFH5AL.mjs +8 -0
  5. package/dist/_dts/api/src/bookingOperations.d.ts +7 -0
  6. package/dist/_dts/api/src/bookingRecords.d.ts +3 -0
  7. package/dist/_dts/api/src/cache-tags.d.ts +4 -0
  8. package/dist/_dts/api/src/contentRuntime.d.ts +14 -0
  9. package/dist/_dts/api/src/endpoints.d.ts +8 -0
  10. package/dist/_dts/api/src/event-presentation.d.ts +15 -0
  11. package/dist/_dts/api/src/index.d.ts +1 -1
  12. package/dist/_dts/api/src/public/endpoints.d.ts +1 -1
  13. package/dist/_dts/api/src/publicExperienceEndpoints.d.ts +17 -4
  14. package/dist/_dts/api/src/shared-contracts.d.ts +19 -9
  15. package/dist/_dts/api/src/siteRuntimeEndpoints.d.ts +6 -1
  16. package/dist/_dts/blocks/src/portal/types.d.ts +7 -0
  17. package/dist/_dts/blocks/src/system/runtime/api/index.d.ts +1 -1
  18. package/dist/_dts/blocks/src/system/runtime/api/types.d.ts +21 -6
  19. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/TicketSelectionStep.d.ts +5 -3
  20. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/event-registration.headless.contract.d.ts +5 -1
  21. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/hooks.d.ts +2 -2
  22. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/sessionStorage.d.ts +9 -6
  23. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.d.ts +36 -0
  24. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/types.d.ts +7 -1
  25. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationStripeReturn.d.ts +2 -2
  26. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationSubmit.d.ts +2 -3
  27. package/dist/_dts/blocks/src/system/runtime/nodes/event-registration/useEventRegistrationWizard.d.ts +19 -4
  28. package/dist/_dts/content-model/src/entry-slots/types.d.ts +6 -0
  29. package/dist/_dts/db/src/generated/supabase/database.types.d.ts +163 -2
  30. package/dist/_dts/media-core/src/assetsInUseSummary.d.ts +7 -0
  31. package/dist/_dts/media-core/src/mediaUsageProjection.d.ts +29 -1
  32. package/dist/_dts/sdk/src/client/types.d.ts +7 -2
  33. package/dist/_dts/sdk/src/next/middleware.d.ts +1 -0
  34. package/dist/_dts/sdk/src/next/redirect-destination.d.ts +3 -0
  35. package/dist/_dts/sdk/src/next/route-outcome-proxy.d.ts +22 -0
  36. package/dist/_dts/sdk/src/public-api/contracts.d.ts +20 -1
  37. package/dist/_dts/sdk/src/public-api/runtime.d.ts +1 -1
  38. package/dist/_dts/sdk/src/rendering/helpers/loadContent.d.ts +9 -1
  39. package/dist/_dts/sdk/src/version.d.ts +1 -1
  40. package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
  41. package/dist/{chunk-XIK2HYI3.mjs → chunk-4A6NWO3X.mjs} +7 -7
  42. package/dist/{chunk-NT76QGIJ.mjs → chunk-7ED5UNHL.mjs} +14 -0
  43. package/dist/{chunk-AQ6RT6LZ.mjs → chunk-K2SJTZMX.mjs} +2 -2
  44. package/dist/{chunk-A63UXN3P.mjs → chunk-PJPFTVZG.mjs} +2 -2
  45. package/dist/{chunk-AJZGT6YG.mjs → chunk-VTPPVAOL.mjs} +1 -1
  46. package/dist/{chunk-PCNCETBI.mjs → chunk-VZBSD4AJ.mjs} +449 -183
  47. package/dist/cli/index.mjs +176 -73
  48. package/dist/client/{accordion-RNQIPG55.mjs → accordion-LGO4IO5U.mjs} +1 -1
  49. package/dist/client/{bookingForm-HYEEVRLP.mjs → bookingForm-MWLS4LGU.mjs} +3 -3
  50. package/dist/client/bookings.mjs +211 -105
  51. package/dist/client/{carousel-W7DQ3YPU.mjs → carousel-6AHNP5HA.mjs} +1 -1
  52. package/dist/client/{cart-PXJ3SXXO.mjs → cart-CIXGYAZB.mjs} +4 -4
  53. package/dist/client/{checkout-X3JYSOXI.mjs → checkout-OTX6O3DN.mjs} +4 -4
  54. package/dist/client/{chunk-Q47XGAGS.mjs → chunk-AVBQSUIS.mjs} +1 -1
  55. package/dist/client/{chunk-WERRJXNJ.mjs → chunk-HOP7MKAZ.mjs} +1 -1
  56. package/dist/client/{chunk-IXDSXF2D.mjs → chunk-INRIT6DP.mjs} +353 -187
  57. package/dist/client/{chunk-DUOHLWLF.mjs → chunk-MYCXAKWP.mjs} +2 -2
  58. package/dist/client/{chunk-J6TMMMXW.mjs → chunk-R3NQTBWE.mjs} +1 -1
  59. package/dist/client/{chunk-YYVGSYBO.mjs → chunk-TREIML53.mjs} +3 -3
  60. package/dist/client/{chunk-6SYGVVWA.mjs → chunk-V67AT7ZW.mjs} +3 -0
  61. package/dist/client/{chunk-PICR3NTQ.mjs → chunk-Y2PMKCXO.mjs} +1 -1
  62. package/dist/client/client.mjs +503 -183
  63. package/dist/client/{courseRegistration-QZ67PVHO.mjs → courseRegistration-2LU6C3JL.mjs} +3 -3
  64. package/dist/client/{eventCalendar-V3BDHQNT.mjs → eventCalendar-OKUSISBZ.mjs} +6 -6
  65. package/dist/client/{eventCombined-XYFRN2RG.mjs → eventCombined-CMF6CD4S.mjs} +6 -6
  66. package/dist/client/{eventDetails-TFE5TWKV.mjs → eventDetails-XYWH7WXI.mjs} +6 -6
  67. package/dist/client/{eventListing-IQ6AGWZ6.mjs → eventListing-EK5DJTAH.mjs} +6 -6
  68. package/dist/client/{eventRegistration-WGJVFKQJ.mjs → eventRegistration-XDOSICV6.mjs} +6 -6
  69. package/dist/client/{form-6UZY6RUA.mjs → form-J6GOHKYG.mjs} +1 -1
  70. package/dist/client/{headerSection-RSFXY6V3.mjs → headerSection-C5SYJTRW.mjs} +2 -2
  71. package/dist/client/hooks.mjs +14 -0
  72. package/dist/client/{locationMap-24FH67I4.mjs → locationMap-QSQIFVVT.mjs} +2 -2
  73. package/dist/client/{newsletterForm-QVL3MLTN.mjs → newsletterForm-47VXJRWK.mjs} +1 -1
  74. package/dist/client/{productDetail-ZDYN3G4T.mjs → productDetail-5U6ISCC2.mjs} +4 -4
  75. package/dist/client/{productList-4BMJLQHZ.mjs → productList-XDZUMR3P.mjs} +4 -4
  76. package/dist/client/rendering/client.mjs +362 -182
  77. package/dist/client/rendering/islands.mjs +18 -18
  78. package/dist/client/rendering.mjs +489 -182
  79. package/dist/client/{shop-U5C634FF.mjs → shop-ZSZKIY3R.mjs} +4 -4
  80. package/dist/preview-next/before-render.mjs +14 -0
  81. package/dist/preview-next/client/runtime.mjs +5 -5
  82. package/dist/preview-next/middleware.mjs +14 -0
  83. package/dist/{sdk-runtime-VGV46WVB.mjs → sdk-runtime-PHNM7MRD.mjs} +6 -6
  84. package/dist/server/components.mjs +111 -0
  85. package/dist/server/config-validation.mjs +14 -0
  86. package/dist/server/config.mjs +14 -0
  87. package/dist/server/data.mjs +14 -0
  88. package/dist/server/index.mjs +44 -1
  89. package/dist/server/next/middleware.mjs +120 -0
  90. package/dist/server/next.mjs +225 -2
  91. package/dist/server/prebuild.mjs +1 -1
  92. package/dist/server/rendering/server.mjs +111 -0
  93. package/dist/server/rendering.mjs +127 -0
  94. package/dist/server/routing.mjs +14 -0
  95. package/dist/server/server.mjs +60 -1
  96. package/dist/server/theme-bridge.mjs +97 -0
  97. package/dist/styles/index.css +95 -0
  98. package/package.json +5 -1
  99. package/dist/SdkPreviewModeRuntime-NHTLRNQD.mjs +0 -8
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { unwrapResponse, ApiRequestError, ApiEnvelopeError, extractApiEnvelopeErrorDetails } from './chunk-NT76QGIJ.mjs';
2
+ import { unwrapResponse, ApiRequestError, ApiEnvelopeError, extractApiEnvelopeErrorDetails } from './chunk-7ED5UNHL.mjs';
3
3
  import { __commonJS, __export, __toESM } from './chunk-R7POPVJR.mjs';
4
4
  import * as React61 from 'react';
5
5
  import React61__default, { createContext, forwardRef, useContext, createElement, useRef, useMemo, useEffect, useState, useCallback, useImperativeHandle, useLayoutEffect, memo, createRef, Component as Component$1, useDebugValue, useId as useId$1, useSyncExternalStore, isValidElement, cloneElement, useReducer, startTransition } from 'react';
@@ -60965,6 +60965,9 @@ function resolveDiscountContext(input) {
60965
60965
  if (!input.eventRequiresPayment) {
60966
60966
  return null;
60967
60967
  }
60968
+ if (input.ticketsCount <= 0) {
60969
+ return null;
60970
+ }
60968
60971
  if (selection?.method && selection.method !== "stripe") {
60969
60972
  return null;
60970
60973
  }
@@ -86861,6 +86864,103 @@ var containerResponsiveThemeCss = `/*
86861
86864
  line-height: 1.35;
86862
86865
  }
86863
86866
 
86867
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-lines {
86868
+ display: grid;
86869
+ gap: 0.75rem;
86870
+ }
86871
+
86872
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-list {
86873
+ display: grid;
86874
+ gap: 0.625rem;
86875
+ }
86876
+
86877
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line {
86878
+ display: grid;
86879
+ grid-template-columns: minmax(0, 1fr) auto;
86880
+ align-items: center;
86881
+ gap: 0.875rem;
86882
+ min-height: 4.75rem;
86883
+ padding: 0.875rem;
86884
+ border: 1px solid var(--er-border);
86885
+ border-radius: calc(var(--rb-block-radius) * 0.75);
86886
+ background: var(--er-control-bg);
86887
+ }
86888
+
86889
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line--disabled {
86890
+ color: var(--er-on-surface-disabled);
86891
+ background: var(--er-surface-muted);
86892
+ }
86893
+
86894
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-main {
86895
+ min-width: 0;
86896
+ display: grid;
86897
+ gap: 0.25rem;
86898
+ }
86899
+
86900
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-heading {
86901
+ display: flex;
86902
+ align-items: baseline;
86903
+ justify-content: space-between;
86904
+ gap: 0.75rem;
86905
+ }
86906
+
86907
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-name {
86908
+ min-width: 0;
86909
+ font-size: 0.9375rem;
86910
+ font-weight: 600;
86911
+ line-height: 1.3;
86912
+ overflow-wrap: anywhere;
86913
+ }
86914
+
86915
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-price {
86916
+ flex-shrink: 0;
86917
+ font-size: 0.875rem;
86918
+ font-weight: 600;
86919
+ line-height: 1.3;
86920
+ }
86921
+
86922
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-detail {
86923
+ margin: 0;
86924
+ color: var(--er-on-surface-muted);
86925
+ font-size: 0.8125rem;
86926
+ line-height: 1.35;
86927
+ overflow-wrap: anywhere;
86928
+ }
86929
+
86930
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-summary {
86931
+ padding: 0.75rem 0.875rem;
86932
+ border: 1px solid var(--er-summary-border);
86933
+ border-radius: calc(var(--rb-block-radius) * 0.75);
86934
+ background: var(--er-surface-muted);
86935
+ }
86936
+
86937
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-summary-title {
86938
+ font-size: 0.875rem;
86939
+ font-weight: 600;
86940
+ line-height: 1.35;
86941
+ }
86942
+
86943
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line-summary-list {
86944
+ margin: 0.25rem 0 0;
86945
+ padding-left: 1.125rem;
86946
+ color: var(--er-on-surface-muted);
86947
+ font-size: 0.8125rem;
86948
+ line-height: 1.4;
86949
+ }
86950
+
86951
+ @container rb-site (max-width: 639px) {
86952
+ @scope ([data-rb-responsive-mode="container"]) to (:scope [data-rb-responsive-mode]) {
86953
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line {
86954
+ grid-template-columns: minmax(0, 1fr);
86955
+ align-items: stretch;
86956
+ }
86957
+
86958
+ :where(.theme-scope) :where([data-block="event-registration"]) .er-ticket-line .er-counter {
86959
+ justify-content: flex-start;
86960
+ }
86961
+ }
86962
+ }
86963
+
86864
86964
  /* Form inputs */
86865
86965
  :where(.theme-scope) :where([data-block="event-registration"]) .er-input {
86866
86966
  width: 100%;
@@ -131910,6 +132010,7 @@ function transformEligibilityResponse(response) {
131910
132010
  requiresPayment: eligibility.requiresPayment,
131911
132011
  pricingMode: eligibility.pricingMode,
131912
132012
  selectedTicketTypeId: eligibility.selectedTicketTypeId ?? null,
132013
+ ticketLines: eligibility.ticketLines ?? [],
131913
132014
  ticketTypes: eligibility.ticketTypes ?? [],
131914
132015
  isFull: eligibility.isFull,
131915
132016
  waitlistEnabled: eligibility.waitlistEnabled,
@@ -131966,6 +132067,72 @@ function findSelectedTicketType(input) {
131966
132067
  }).find((ticketType) => ticketType.id === input.selectedTicketTypeId) ?? null;
131967
132068
  }
131968
132069
 
132070
+ // ../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts
132071
+ function normalizeTicketSelectionLines(lines) {
132072
+ const quantitiesByTicketType = /* @__PURE__ */ new Map();
132073
+ for (const line of lines) {
132074
+ const quantity = Math.floor(line.quantity);
132075
+ if (quantity <= 0) continue;
132076
+ const key = line.ticketTypeId ?? null;
132077
+ quantitiesByTicketType.set(key, (quantitiesByTicketType.get(key) ?? 0) + quantity);
132078
+ }
132079
+ return [...quantitiesByTicketType.entries()].map(([ticketTypeId, quantity]) => ({
132080
+ ticketTypeId,
132081
+ quantity
132082
+ }));
132083
+ }
132084
+ function getTicketSelectionTotal(lines) {
132085
+ return normalizeTicketSelectionLines(lines).reduce((total, line) => total + line.quantity, 0);
132086
+ }
132087
+ function getTicketLineQuantity(lines, ticketTypeId) {
132088
+ return normalizeTicketSelectionLines(lines).find((line) => line.ticketTypeId === ticketTypeId)?.quantity ?? 0;
132089
+ }
132090
+ function getSingleSelectedTicketTypeId(lines) {
132091
+ const normalized = normalizeTicketSelectionLines(lines);
132092
+ return normalized.length === 1 ? normalized[0]?.ticketTypeId ?? null : null;
132093
+ }
132094
+ function setTicketLineQuantity(input) {
132095
+ const nextQuantity = Math.max(0, Math.floor(input.quantity));
132096
+ const normalized = normalizeTicketSelectionLines(input.lines).filter((line) => line.ticketTypeId !== input.ticketTypeId);
132097
+ if (nextQuantity <= 0) {
132098
+ return normalized;
132099
+ }
132100
+ return [
132101
+ ...normalized,
132102
+ {
132103
+ ticketTypeId: input.ticketTypeId,
132104
+ quantity: nextQuantity
132105
+ }
132106
+ ];
132107
+ }
132108
+ function getTicketTypeLineMax(input) {
132109
+ const otherSelectedQuantity = input.totalSelectedQuantity - input.currentQuantity;
132110
+ const maxByBlock = input.maxTickets - otherSelectedQuantity;
132111
+ const maxByEvent = input.availableSpots == null ? input.maxTickets : input.availableSpots - otherSelectedQuantity;
132112
+ const maxByTicketType = input.ticketTypeRemainingCapacity ?? input.maxTickets;
132113
+ return Math.max(0, Math.min(maxByBlock, maxByEvent, maxByTicketType));
132114
+ }
132115
+ function calculateTicketSelectionSubtotal(input) {
132116
+ return normalizeTicketSelectionLines(input.lines).reduce((subtotal, line) => {
132117
+ const ticketType = line.ticketTypeId ? input.ticketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null : null;
132118
+ const unitPriceCents = ticketType?.priceAmount ?? input.fallbackUnitPriceCents;
132119
+ return subtotal + Math.max(0, unitPriceCents) * line.quantity;
132120
+ }, 0);
132121
+ }
132122
+ function buildTicketSelectionSummaryLines(input) {
132123
+ return normalizeTicketSelectionLines(input.lines).map((line) => {
132124
+ const ticketType = line.ticketTypeId ? input.ticketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null : null;
132125
+ const unitPriceCents = ticketType?.priceAmount ?? input.fallbackUnitPriceCents;
132126
+ return {
132127
+ ticketTypeId: line.ticketTypeId,
132128
+ label: ticketType?.name ?? input.fallbackLabel,
132129
+ quantity: line.quantity,
132130
+ unitPriceCents,
132131
+ subtotalCents: Math.max(0, unitPriceCents) * line.quantity
132132
+ };
132133
+ });
132134
+ }
132135
+
131969
132136
  // ../blocks/src/system/runtime/nodes/event-registration/hooks.state.ts
131970
132137
  var initialEligibilityLookupState = {
131971
132138
  tag: "idle",
@@ -132072,6 +132239,7 @@ function usePortalAuth() {
132072
132239
  }
132073
132240
  function useEligibility(params) {
132074
132241
  const api = useBlockApi();
132242
+ const ticketSelectionsKey = JSON.stringify(normalizeTicketSelectionLines(params.ticketSelections));
132075
132243
  const [state, dispatch] = useReducer(
132076
132244
  eligibilityLookupReducer,
132077
132245
  initialEligibilityLookupState
@@ -132085,7 +132253,8 @@ function useEligibility(params) {
132085
132253
  dispatch({ type: "reset" });
132086
132254
  return;
132087
132255
  }
132088
- if (!params.occurrenceId || !params.email.trim()) {
132256
+ const ticketSelections = JSON.parse(ticketSelectionsKey);
132257
+ if (!params.occurrenceId || !params.email.trim() || ticketSelections.length === 0) {
132089
132258
  requestIdRef.current += 1;
132090
132259
  dispatch({ type: "reset" });
132091
132260
  return;
@@ -132097,10 +132266,9 @@ function useEligibility(params) {
132097
132266
  const result = await api.checkEligibility({
132098
132267
  occurrenceId: params.occurrenceId,
132099
132268
  attendanceMode: params.attendanceMode ?? void 0,
132100
- ticketTypeId: params.ticketTypeId ?? void 0,
132269
+ ticketSelections,
132101
132270
  selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
132102
- email: params.email.trim().toLowerCase(),
132103
- ticketsCount: params.ticketsCount
132271
+ email: params.email.trim().toLowerCase()
132104
132272
  });
132105
132273
  if (cancelled || requestIdRef.current !== requestId) return;
132106
132274
  dispatch({
@@ -132119,7 +132287,15 @@ function useEligibility(params) {
132119
132287
  return () => {
132120
132288
  cancelled = true;
132121
132289
  };
132122
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
132290
+ }, [
132291
+ api,
132292
+ params.enabled,
132293
+ params.occurrenceId,
132294
+ params.attendanceMode,
132295
+ ticketSelectionsKey,
132296
+ params.selectedSlidingAmount,
132297
+ params.email
132298
+ ]);
132123
132299
  return {
132124
132300
  state,
132125
132301
  eligibility: state.eligibility,
@@ -132195,9 +132371,15 @@ function savePendingRegistration(siteId, occurrenceId, data) {
132195
132371
  }
132196
132372
  try {
132197
132373
  const key = getStorageKey(siteId, occurrenceId);
132374
+ const ticketSelections = normalizeTicketSelectionLines(data.ticketSelections);
132375
+ if (ticketSelections.length === 0) {
132376
+ console.warn("[sessionStorage] No ticket selections to save, skipping registration state");
132377
+ return;
132378
+ }
132198
132379
  const entry = {
132199
132380
  ...data,
132200
- version: 3,
132381
+ ticketSelections,
132382
+ version: 4,
132201
132383
  timestamp: Date.now()
132202
132384
  };
132203
132385
  sessionStorage.setItem(key, JSON.stringify(entry));
@@ -132243,8 +132425,8 @@ function parseAndValidateEntry(stored, key) {
132243
132425
  return null;
132244
132426
  }
132245
132427
  const version2 = raw.version;
132246
- if (version2 !== 1 && version2 !== 2 && version2 !== 3) {
132247
- console.warn("[sessionStorage] Unknown version, ignoring entry");
132428
+ if (version2 !== 4) {
132429
+ console.warn("[sessionStorage] Outdated version, ignoring entry");
132248
132430
  sessionStorage.removeItem(key);
132249
132431
  return null;
132250
132432
  }
@@ -132260,34 +132442,12 @@ function parseAndValidateEntry(stored, key) {
132260
132442
  sessionStorage.removeItem(key);
132261
132443
  return null;
132262
132444
  }
132263
- if (entry.version === 1) {
132264
- const legacy = entry;
132265
- return {
132266
- version: 3,
132267
- attendeeId: legacy.attendeeId,
132268
- formData: legacy.formData,
132269
- ticketsCount: legacy.ticketsCount,
132270
- selectedEventId: legacy.selectedEventId,
132271
- selectedAttendanceMode: null,
132272
- selectedTicketTypeId: null,
132273
- paymentSelection: { method: legacy.paymentMethod },
132274
- timestamp: legacy.timestamp
132275
- };
132276
- }
132277
- if (entry.version === 2) {
132278
- return {
132279
- version: 3,
132280
- attendeeId: entry.attendeeId,
132281
- formData: entry.formData,
132282
- ticketsCount: entry.ticketsCount,
132283
- selectedEventId: entry.selectedEventId,
132284
- selectedAttendanceMode: null,
132285
- selectedTicketTypeId: null,
132286
- paymentSelection: entry.paymentSelection,
132287
- timestamp: entry.timestamp
132288
- };
132445
+ const ticketSelections = normalizeTicketSelectionLines(entry.ticketSelections);
132446
+ if (ticketSelections.length === 0 || getTicketSelectionTotal(ticketSelections) <= 0) {
132447
+ sessionStorage.removeItem(key);
132448
+ return null;
132289
132449
  }
132290
- return entry;
132450
+ return { ...entry, ticketSelections };
132291
132451
  } catch {
132292
132452
  console.warn("[sessionStorage] Failed to parse entry, removing");
132293
132453
  sessionStorage.removeItem(key);
@@ -132319,8 +132479,9 @@ function cleanupStaleRegistrations() {
132319
132479
  const stored = sessionStorage.getItem(key);
132320
132480
  if (!stored) continue;
132321
132481
  const entry = JSON.parse(stored);
132322
- const age = Date.now() - entry.timestamp;
132323
- if (age > STALE_THRESHOLD_MS) {
132482
+ const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
132483
+ const age = Date.now() - timestamp;
132484
+ if (!Number.isFinite(timestamp) || age > STALE_THRESHOLD_MS) {
132324
132485
  keysToRemove.push(key);
132325
132486
  }
132326
132487
  } catch {
@@ -132408,14 +132569,16 @@ function getEventAttendanceModeLabel(mode) {
132408
132569
  }
132409
132570
  }
132410
132571
  function TicketCounter(props2) {
132411
- const { ticketsCount, maxAllowed, onTicketsChange } = props2;
132572
+ const { ticketsCount, maxAllowed, label, onTicketsChange } = props2;
132573
+ const minAllowed = props2.minAllowed ?? 1;
132412
132574
  return /* @__PURE__ */ jsxs("div", { className: "er-row er-counter", children: [
132413
132575
  /* @__PURE__ */ jsx(
132414
132576
  "button",
132415
132577
  {
132416
132578
  type: "button",
132417
- onClick: () => onTicketsChange(Math.max(1, ticketsCount - 1)),
132418
- disabled: ticketsCount <= 1,
132579
+ "aria-label": `Decrease ${label}`,
132580
+ onClick: () => onTicketsChange(Math.max(minAllowed, ticketsCount - 1)),
132581
+ disabled: ticketsCount <= minAllowed,
132419
132582
  className: "er-counter-btn",
132420
132583
  children: "-"
132421
132584
  }
@@ -132425,6 +132588,7 @@ function TicketCounter(props2) {
132425
132588
  "button",
132426
132589
  {
132427
132590
  type: "button",
132591
+ "aria-label": `Increase ${label}`,
132428
132592
  onClick: () => onTicketsChange(Math.min(maxAllowed, ticketsCount + 1)),
132429
132593
  disabled: ticketsCount >= maxAllowed,
132430
132594
  className: "er-counter-btn",
@@ -132477,14 +132641,86 @@ function SlidingScaleAmountField(props2) {
132477
132641
  props2.message ? /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: props2.message }) : null
132478
132642
  ] });
132479
132643
  }
132480
- function getTicketTypeCounterAvailabilityLabel(input) {
132481
- if (input.selectedRemainingCapacity !== null) {
132482
- return `${input.selectedRemainingCapacity} remaining for this ticket type`;
132483
- }
132484
- if (input.eventAvailableSpots != null) {
132485
- return `${input.eventAvailableSpots} spots available`;
132486
- }
132487
- return null;
132644
+ function TicketTypeQuantityRows(props2) {
132645
+ const totalSelectedQuantity = getTicketSelectionTotal(props2.ticketSelections);
132646
+ const selectedTicketTypes = props2.ticketTypes.filter(
132647
+ (ticketType) => getTicketLineQuantity(props2.ticketSelections, ticketType.id) > 0
132648
+ );
132649
+ return /* @__PURE__ */ jsxs("div", { className: "er-ticket-lines", children: [
132650
+ /* @__PURE__ */ jsx("div", { className: "er-ticket-line-list", role: "list", "aria-label": "Tickets", children: props2.ticketTypes.map((ticketType) => {
132651
+ const quantity = getTicketLineQuantity(props2.ticketSelections, ticketType.id);
132652
+ const disabled = ticketType.availability.kind === "unavailable";
132653
+ const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
132654
+ const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, props2.currency) : "Free";
132655
+ const maxAllowed = disabled ? 0 : getTicketTypeLineMax({
132656
+ currentQuantity: quantity,
132657
+ totalSelectedQuantity,
132658
+ maxTickets: props2.maxTickets,
132659
+ availableSpots: props2.availableSpots,
132660
+ ticketTypeRemainingCapacity: ticketType.availability.remainingCapacity
132661
+ });
132662
+ return /* @__PURE__ */ jsxs(
132663
+ "div",
132664
+ {
132665
+ className: `er-ticket-line${disabled ? " er-ticket-line--disabled" : ""}`,
132666
+ role: "listitem",
132667
+ children: [
132668
+ /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-main", children: [
132669
+ /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-heading", children: [
132670
+ /* @__PURE__ */ jsx("span", { className: "er-ticket-line-name", children: ticketType.name }),
132671
+ /* @__PURE__ */ jsx("span", { className: "er-ticket-line-price", children: priceLabel })
132672
+ ] }),
132673
+ ticketType.description ? /* @__PURE__ */ jsx("p", { className: "er-ticket-line-detail", children: ticketType.description }) : null,
132674
+ availabilityLabel ? /* @__PURE__ */ jsx("p", { className: "er-ticket-line-detail", children: availabilityLabel }) : ticketType.availability.remainingCapacity !== null ? /* @__PURE__ */ jsxs("p", { className: "er-ticket-line-detail", children: [
132675
+ ticketType.availability.remainingCapacity,
132676
+ " remaining for this ticket type"
132677
+ ] }) : null
132678
+ ] }),
132679
+ /* @__PURE__ */ jsx(
132680
+ TicketCounter,
132681
+ {
132682
+ ticketsCount: quantity,
132683
+ minAllowed: 0,
132684
+ maxAllowed,
132685
+ label: ticketType.name,
132686
+ onTicketsChange: (nextQuantity) => {
132687
+ props2.onTicketSelectionsChange?.(setTicketLineQuantity({
132688
+ lines: props2.ticketSelections,
132689
+ ticketTypeId: ticketType.id,
132690
+ quantity: nextQuantity
132691
+ }));
132692
+ }
132693
+ }
132694
+ )
132695
+ ]
132696
+ },
132697
+ ticketType.id
132698
+ );
132699
+ }) }),
132700
+ /* @__PURE__ */ jsx(
132701
+ TicketCounterLimit,
132702
+ {
132703
+ maximumLabel: `${props2.maxTickets} tickets maximum`,
132704
+ availabilityLabel: props2.availableSpots != null ? `${props2.availableSpots} spots available` : null
132705
+ }
132706
+ ),
132707
+ selectedTicketTypes.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-summary", "aria-live": "polite", children: [
132708
+ /* @__PURE__ */ jsxs("div", { className: "er-ticket-line-summary-title", children: [
132709
+ totalSelectedQuantity,
132710
+ " ticket",
132711
+ totalSelectedQuantity === 1 ? "" : "s",
132712
+ " selected"
132713
+ ] }),
132714
+ /* @__PURE__ */ jsx("ul", { className: "er-ticket-line-summary-list", children: selectedTicketTypes.map((ticketType) => {
132715
+ const quantity = getTicketLineQuantity(props2.ticketSelections, ticketType.id);
132716
+ return /* @__PURE__ */ jsxs("li", { children: [
132717
+ quantity,
132718
+ " x ",
132719
+ ticketType.name
132720
+ ] }, ticketType.id);
132721
+ }) })
132722
+ ] }) : props2.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." })
132723
+ ] });
132488
132724
  }
132489
132725
  function getAttendanceModeDetail(attendanceMode) {
132490
132726
  if (attendanceMode.availableSpots === null) {
@@ -132502,6 +132738,7 @@ function AttendanceModeTiles(props2) {
132502
132738
  "button",
132503
132739
  {
132504
132740
  type: "button",
132741
+ "aria-pressed": isSelected,
132505
132742
  className: `er-payment-tile${isSelected ? " er-payment-tile--selected" : ""}`,
132506
132743
  onClick: () => props2.onAttendanceModeChange?.(attendanceMode.mode),
132507
132744
  children: [
@@ -132538,8 +132775,9 @@ var TicketSelectionStep = ({
132538
132775
  onAttendanceModeChange,
132539
132776
  ticketsCount,
132540
132777
  maxTickets,
132541
- selectedTicketTypeId = null,
132542
- onTicketTypeChange,
132778
+ visibleTicketTypes: visibleTicketTypesOverride,
132779
+ ticketSelections = [],
132780
+ onTicketSelectionsChange,
132543
132781
  slidingScale = null,
132544
132782
  selectedSlidingAmountInput = "",
132545
132783
  slidingScaleSelectionMessage = null,
@@ -132557,28 +132795,22 @@ var TicketSelectionStep = ({
132557
132795
  const eventPricingMode = eventPricing ? publicEventPricingMode(eventPricing) : void 0;
132558
132796
  const eventPricingCurrency = eventPricing ? publicEventPricingCurrency(eventPricing) : null;
132559
132797
  const usesTicketTypes = eventPricingMode === "ticket_types" && !canJoinWaitlist;
132560
- const visibleTicketTypes = usesTicketTypes ? getVisibleTicketTypes({
132798
+ const visibleTicketTypes = usesTicketTypes ? visibleTicketTypesOverride ?? getVisibleTicketTypes({
132561
132799
  event,
132562
132800
  eligibility: null,
132563
132801
  selectedAttendanceMode
132564
132802
  }) : [];
132565
- const selectedTicketType = usesTicketTypes ? visibleTicketTypes.find((ticketType) => ticketType.id === selectedTicketTypeId) ?? null : null;
132566
132803
  const implicitTicketType = usesTicketTypes ? getImplicitSingleAvailableTicketType(visibleTicketTypes) : null;
132567
- const effectiveSelectedTicketType = selectedTicketType ?? implicitTicketType;
132568
- const shouldShowTicketTypeTiles = usesTicketTypes && implicitTicketType === null;
132569
- const selectedRemainingCapacity = effectiveSelectedTicketType?.availability.remainingCapacity ?? null;
132570
- const noSellableTicketTypes = usesTicketTypes && (visibleTicketTypes.length === 0 || visibleTicketTypes.every((ticketType) => ticketType.availability.kind === "unavailable"));
132804
+ const noVisibleTicketTypes = usesTicketTypes && visibleTicketTypes.length === 0;
132805
+ const hasAvailableTicketTypes = usesTicketTypes && visibleTicketTypes.some((ticketType) => ticketType.availability.kind === "available");
132806
+ const aggregateRemainingCapacity = implicitTicketType?.availability.remainingCapacity ?? null;
132571
132807
  const maxAllowed = getMaxAllowedTicketCount({
132572
132808
  canJoinWaitlist,
132573
132809
  usesTicketTypes,
132574
132810
  maxTickets,
132575
132811
  availableSpots,
132576
- selectedTicketTypeRemainingCapacity: selectedRemainingCapacity,
132577
- hasSelectedTicketType: effectiveSelectedTicketType !== null
132578
- });
132579
- const selectedTicketTypeAvailabilityLabel = getTicketTypeCounterAvailabilityLabel({
132580
- selectedRemainingCapacity,
132581
- eventAvailableSpots: event?.availableSpots
132812
+ selectedTicketTypeRemainingCapacity: aggregateRemainingCapacity,
132813
+ hasSelectedTicketType: implicitTicketType !== null
132582
132814
  });
132583
132815
  const eventAvailabilityLabel = !canJoinWaitlist && event?.availableSpots != null ? `${event.availableSpots} spots available` : null;
132584
132816
  if (deliveryMode === "hybrid" && !selectedAttendanceMode) {
@@ -132595,7 +132827,7 @@ var TicketSelectionStep = ({
132595
132827
  /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "Choose whether you want to attend in person or online." })
132596
132828
  ] });
132597
132829
  }
132598
- if (isSoldOut && !canJoinWaitlist && !usesTicketTypes) {
132830
+ if (isSoldOut && !canJoinWaitlist) {
132599
132831
  return /* @__PURE__ */ jsxs("div", { className: "er-stack", children: [
132600
132832
  /* @__PURE__ */ jsx("h3", { className: "er-title", children: "Sold Out" }),
132601
132833
  /* @__PURE__ */ jsx("p", { className: "er-text er-text--muted", children: "Sorry you missed out! This event is fully booked." })
@@ -132613,52 +132845,21 @@ var TicketSelectionStep = ({
132613
132845
  }
132614
132846
  )
132615
132847
  ] }) : null,
132616
- usesTicketTypes ? /* @__PURE__ */ jsxs(Fragment, { children: [
132617
- shouldShowTicketTypeTiles ? /* @__PURE__ */ jsxs(Fragment, { children: [
132618
- /* @__PURE__ */ jsx("h3", { className: "er-title", children: "Choose a ticket type" }),
132619
- /* @__PURE__ */ jsx("div", { className: "er-payment-tiles", role: "list", "aria-label": "Ticket types", children: visibleTicketTypes.map((ticketType) => {
132620
- const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
132621
- const disabled = ticketType.availability.kind === "unavailable";
132622
- const isSelected = selectedTicketTypeId === ticketType.id;
132623
- const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2) : "Free";
132624
- return /* @__PURE__ */ jsx("div", { role: "listitem", children: /* @__PURE__ */ jsxs(
132625
- "button",
132626
- {
132627
- type: "button",
132628
- className: `er-payment-tile${isSelected && !disabled ? " er-payment-tile--selected" : ""}${disabled ? " er-payment-tile--disabled" : ""}`,
132629
- disabled,
132630
- onClick: () => onTicketTypeChange?.(ticketType.id),
132631
- children: [
132632
- /* @__PURE__ */ jsx("div", { className: "er-payment-tile-label", children: ticketType.name }),
132633
- /* @__PURE__ */ jsx("div", { className: "er-payment-tile-detail", children: priceLabel }),
132634
- ticketType.description ? /* @__PURE__ */ jsx("div", { className: "er-payment-tile-detail", children: ticketType.description }) : null,
132635
- availabilityLabel ? /* @__PURE__ */ jsx("div", { className: "er-payment-tile-detail", children: availabilityLabel }) : null
132636
- ]
132637
- }
132638
- ) }, ticketType.id);
132639
- }) })
132640
- ] }) : null,
132641
- 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: [
132642
- /* @__PURE__ */ jsx("h3", { className: "er-title", children: "How many tickets?" }),
132643
- /* @__PURE__ */ jsxs("div", { className: "er-ticket-counter-group", children: [
132644
- /* @__PURE__ */ jsx(
132645
- TicketCounter,
132646
- {
132647
- ticketsCount,
132648
- maxAllowed,
132649
- onTicketsChange
132650
- }
132651
- ),
132652
- /* @__PURE__ */ jsx(
132653
- TicketCounterLimit,
132654
- {
132655
- maximumLabel: `${maxAllowed} tickets maximum`,
132656
- availabilityLabel: selectedTicketTypeAvailabilityLabel
132657
- }
132658
- )
132659
- ] })
132660
- ] })
132661
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
132848
+ 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: [
132849
+ /* @__PURE__ */ jsx("h3", { className: "er-title", children: "Choose tickets" }),
132850
+ /* @__PURE__ */ jsx(
132851
+ TicketTypeQuantityRows,
132852
+ {
132853
+ ticketTypes: visibleTicketTypes,
132854
+ ticketSelections,
132855
+ maxTickets,
132856
+ availableSpots,
132857
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
132858
+ hasAvailableTicketTypes,
132859
+ onTicketSelectionsChange
132860
+ }
132861
+ )
132862
+ ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
132662
132863
  slidingScale ? /* @__PURE__ */ jsx(
132663
132864
  SlidingScaleAmountField,
132664
132865
  {
@@ -132676,6 +132877,7 @@ var TicketSelectionStep = ({
132676
132877
  {
132677
132878
  ticketsCount,
132678
132879
  maxAllowed,
132880
+ label: canJoinWaitlist ? "waitlist spots" : "tickets",
132679
132881
  onTicketsChange
132680
132882
  }
132681
132883
  ),
@@ -133119,7 +133321,7 @@ var PaymentSelectionStep = ({
133119
133321
  }) => {
133120
133322
  const occurrenceId = occurrenceContext?.id ?? event?.registrationOccurrenceId;
133121
133323
  const selectedMethod = selectedSelection.method;
133122
- const totalPrice = eligibility?.price ? eligibility.price * ticketsCount : 0;
133324
+ const totalPrice = eligibility?.price ?? 0;
133123
133325
  const canShowDirectPaymentOptions = Boolean(
133124
133326
  eligibility?.eligible && eligibility.canPayDirectly && eligibility.price
133125
133327
  );
@@ -133519,8 +133721,9 @@ function EventRegistrationWizardContent(props2) {
133519
133721
  onAttendanceModeChange: wizard.setSelectedAttendanceMode,
133520
133722
  ticketsCount: wizard.ticketsCount,
133521
133723
  maxTickets: wizard.maxTicketsNum,
133522
- selectedTicketTypeId: wizard.selectedTicketTypeId,
133523
- onTicketTypeChange: wizard.setSelectedTicketTypeId,
133724
+ visibleTicketTypes: wizard.eligibility ? wizard.visibleTicketTypes : void 0,
133725
+ ticketSelections: wizard.selectedTicketLines,
133726
+ onTicketSelectionsChange: wizard.setSelectedTicketLines,
133524
133727
  slidingScale: wizard.selectedEventSlidingScale,
133525
133728
  selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
133526
133729
  slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
@@ -133628,9 +133831,9 @@ function EventRegistrationWizardActions(props2) {
133628
133831
  const isWaitingForEligibility = wizard.portalAuthState === "authenticated" && wizard.eligibilityLoading;
133629
133832
  const isCheckingExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistrationLoading;
133630
133833
  const hasExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistration !== null;
133631
- const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidSelectedTicketType;
133834
+ const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidTicketSelection;
133632
133835
  const isContinueDisabled = isWaitingForEligibility || isCheckingExistingRegistration || hasExistingRegistration || isSoldOutWithoutWaitlist || ticketTypeSelectionBlocked;
133633
- const continueLabel = isCheckingExistingRegistration ? "Checking registration..." : hasExistingRegistration ? "Already registered" : isSoldOutWithoutWaitlist ? "Sold out" : ticketTypeSelectionBlocked ? "Choose a ticket type" : "Continue";
133836
+ const continueLabel = isCheckingExistingRegistration ? "Checking registration..." : hasExistingRegistration ? "Already registered" : isSoldOutWithoutWaitlist ? "Sold out" : ticketTypeSelectionBlocked ? "Choose a ticket" : "Continue";
133634
133837
  return /* @__PURE__ */ jsxs("div", { className: "er-actions", children: [
133635
133838
  !wizard.hasPreSelectedContext && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => wizard.setStep("select-event"), className: "er-back-btn", children: "\u2190 Back" }),
133636
133839
  /* @__PURE__ */ jsx(
@@ -133718,13 +133921,17 @@ function EventRegistrationSummaryBar(props2) {
133718
133921
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
133719
133922
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
133720
133923
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
133721
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
133722
133924
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
133723
133925
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
133724
- const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
133926
+ const fallbackSubtotalCents = Math.max(
133927
+ 0,
133928
+ ((summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0) * wizard.ticketsCount
133929
+ );
133930
+ const subtotalCents = Math.max(0, wizard.ticketSelectionSubtotalCents ?? fallbackSubtotalCents);
133725
133931
  const autoAppliedDiscount = wizard.discountCode.trim().length > 0 ? null : wizard.autoAppliedDiscount;
133726
133932
  const autoAppliedDiscountCents = Math.max(0, autoAppliedDiscount?.computedAmountCents ?? 0);
133727
133933
  const discountedTotalCents = Math.max(0, subtotalCents - autoAppliedDiscountCents);
133934
+ const selectedLineSummary = (wizard.ticketSelectionSummaryLines ?? []).filter((line) => line.ticketTypeId !== null).map((line) => `${line.quantity} x ${line.label}`).join(", ");
133728
133935
  const selectedDirectPaymentOption = getSelectedDirectPaymentOption({
133729
133936
  wizard,
133730
133937
  totalCents: discountedTotalCents,
@@ -133761,9 +133968,9 @@ function EventRegistrationSummaryBar(props2) {
133761
133968
  /* @__PURE__ */ jsx("h4", { className: "er-summary-bar-event", children: summaryTitle }),
133762
133969
  /* @__PURE__ */ jsxs("span", { className: "er-summary-bar-meta", children: [
133763
133970
  summaryDate && formatEventDateTime(summaryDate, { timeZone: summaryTimeZone }),
133764
- wizard.selectedTicketType ? /* @__PURE__ */ jsxs(Fragment, { children: [
133971
+ selectedLineSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
133765
133972
  " \xB7 ",
133766
- wizard.selectedTicketType.name
133973
+ selectedLineSummary
133767
133974
  ] }) : null,
133768
133975
  wizard.step !== "select-event" && wizard.step !== "select-tickets" && wizard.ticketsCount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
133769
133976
  " \xB7 ",
@@ -133777,8 +133984,8 @@ function EventRegistrationSummaryBar(props2) {
133777
133984
  teamMemberNames.join(", ")
133778
133985
  ] })
133779
133986
  ] }),
133780
- summaryPrice > 0 && /* @__PURE__ */ jsx("div", { className: "er-summary-bar-pricing", children: productMethod ? /* @__PURE__ */ jsxs(Fragment, { children: [
133781
- /* @__PURE__ */ jsx("span", { className: "er-summary-bar-price er-summary-bar-price--struck", children: formatPrice(summaryPrice * wizard.ticketsCount, summaryCurrency) }),
133987
+ subtotalCents > 0 && /* @__PURE__ */ jsx("div", { className: "er-summary-bar-pricing", children: productMethod ? /* @__PURE__ */ jsxs(Fragment, { children: [
133988
+ /* @__PURE__ */ jsx("span", { className: "er-summary-bar-price er-summary-bar-price--struck", children: formatPrice(subtotalCents, summaryCurrency) }),
133782
133989
  /* @__PURE__ */ jsx("div", { className: "er-summary-bar-included", children: wizard.paymentSelection.method === "pass" || wizard.paymentSelection.method === "pass_bundle" ? "1 pass credit" : "Included in membership" })
133783
133990
  ] }) : flexibleBalanceSummary ? /* @__PURE__ */ jsxs(Fragment, { children: [
133784
133991
  flexibleBalanceSummary.total ? /* @__PURE__ */ jsxs("div", { className: "er-summary-bar-product-detail", children: [
@@ -134012,7 +134219,7 @@ function useEventRegistrationStripeReturn(options) {
134012
134219
  setRegistrationResult,
134013
134220
  setSelectedEvent,
134014
134221
  setSelectedAttendanceMode,
134015
- setSelectedTicketTypeId,
134222
+ setTicketSelections,
134016
134223
  setStep,
134017
134224
  setTicketsCount,
134018
134225
  step
@@ -134031,13 +134238,13 @@ function useEventRegistrationStripeReturn(options) {
134031
134238
  const pending = getPendingRegistration(siteId, occurrenceId || void 0);
134032
134239
  if (!pending) return;
134033
134240
  setFormData(pending.formData);
134034
- setTicketsCount(pending.ticketsCount);
134241
+ setTicketSelections(pending.ticketSelections);
134242
+ setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
134035
134243
  if (pending.selectedEventId) {
134036
134244
  const event = events2.find((e3) => e3.publicEventId === pending.selectedEventId) ?? null;
134037
134245
  if (event) setSelectedEvent(event);
134038
134246
  }
134039
134247
  setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
134040
- setSelectedTicketTypeId(pending.selectedTicketTypeId ?? null);
134041
134248
  setPaymentSelection(pending.paymentSelection);
134042
134249
  if (registrationStatus === "cancelled") {
134043
134250
  setStep("select-payment");
@@ -134089,6 +134296,10 @@ function useEventRegistrationSubmit(options) {
134089
134296
  }
134090
134297
  options.setStep("submitting");
134091
134298
  try {
134299
+ const ticketSelections = normalizeTicketSelectionLines(options.ticketSelections);
134300
+ if (ticketSelections.length === 0) {
134301
+ throw new Error("Choose at least one ticket to continue.");
134302
+ }
134092
134303
  const isBundle = method === "pass_bundle" || method === "membership_bundle";
134093
134304
  const bundleProductId = isBundle ? passId || membershipId : void 0;
134094
134305
  const selectionSnapshot = (() => {
@@ -134117,11 +134328,10 @@ function useEventRegistrationSubmit(options) {
134117
134328
  const result = await options.api.registerForEvent({
134118
134329
  occurrenceId: submitOccurrenceId,
134119
134330
  attendanceMode: options.selectedAttendanceMode ?? void 0,
134120
- ticketTypeId: options.selectedTicketTypeId ?? void 0,
134331
+ ticketSelections,
134121
134332
  selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
134122
134333
  email: options.formData.email.trim().toLowerCase(),
134123
134334
  name: options.formData.name.trim(),
134124
- ticketsCount: options.ticketsCount,
134125
134335
  phone: options.formData.phone.trim() || void 0,
134126
134336
  notes: options.formData.notes.trim() || void 0,
134127
134337
  altchaSolution: options.altchaSolution,
@@ -134166,10 +134376,9 @@ function useEventRegistrationSubmit(options) {
134166
134376
  savePendingRegistration(siteId, submitOccurrenceId, {
134167
134377
  attendeeId: result.attendeeId,
134168
134378
  formData: options.formData,
134169
- ticketsCount: options.ticketsCount,
134379
+ ticketSelections,
134170
134380
  selectedEventId: options.registrationTarget?.selectedEventId ?? null,
134171
134381
  selectedAttendanceMode: options.selectedAttendanceMode,
134172
- selectedTicketTypeId: options.selectedTicketTypeId ?? null,
134173
134382
  paymentSelection: selectionSnapshot
134174
134383
  });
134175
134384
  }
@@ -134281,7 +134490,6 @@ function useEventRegistrationWizard(options) {
134281
134490
  createInitialRegistrationFlowState
134282
134491
  );
134283
134492
  const step = flowState.step;
134284
- const ticketsCount = flowState.ticketsCount;
134285
134493
  const paymentSelection = flowState.paymentSelection;
134286
134494
  const [selectedEvent, setSelectedEvent] = useState(preSelectedEvent ?? null);
134287
134495
  const [formData, setFormData] = useState({ name: "", email: "", phone: "", notes: "" });
@@ -134292,7 +134500,7 @@ function useEventRegistrationWizard(options) {
134292
134500
  const [selectedAttendanceMode, setSelectedAttendanceMode] = useState(
134293
134501
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
134294
134502
  );
134295
- const [selectedTicketTypeId, setSelectedTicketTypeId] = useState(null);
134503
+ const [selectedTicketLines, setSelectedTicketLines] = useState([]);
134296
134504
  const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState(() => {
134297
134505
  const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
134298
134506
  return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
@@ -134328,14 +134536,51 @@ function useEventRegistrationWizard(options) {
134328
134536
  [options.occurrenceContext, selectedEvent]
134329
134537
  );
134330
134538
  const occurrenceId = registrationTarget?.occurrenceId ?? "";
134539
+ const currentEvent = selectedEvent ?? preSelectedEvent;
134540
+ const currentEventPricing = currentEvent ? resolveRuntimeEventPricing(currentEvent) : null;
134541
+ const currentEventUsesTicketTypes = currentEventPricing !== null && publicEventPricingMode(currentEventPricing) === "ticket_types";
134542
+ const selectedModeAvailability = selectedAttendanceMode ? (currentEvent?.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
134543
+ const currentAvailableSpots = selectedModeAvailability?.availableSpots ?? currentEvent?.availableSpots;
134544
+ const currentWaitlistEnabled = selectedModeAvailability?.waitlistEnabled ?? currentEvent?.waitlistEnabled ?? false;
134545
+ const currentCanJoinWaitlist = currentAvailableSpots === 0 && currentWaitlistEnabled === true;
134546
+ const usesTicketTypeSelections = currentEventUsesTicketTypes && !currentCanJoinWaitlist;
134547
+ const eventTicketTypesForSelection = useMemo(
134548
+ () => getVisibleTicketTypes({
134549
+ event: currentEvent,
134550
+ eligibility: null,
134551
+ selectedAttendanceMode
134552
+ }),
134553
+ [currentEvent, selectedAttendanceMode]
134554
+ );
134555
+ const normalizedSelectedTicketLines = useMemo(
134556
+ () => normalizeTicketSelectionLines(selectedTicketLines),
134557
+ [selectedTicketLines]
134558
+ );
134559
+ const ticketTypeSelectionTotal = useMemo(
134560
+ () => getTicketSelectionTotal(normalizedSelectedTicketLines),
134561
+ [normalizedSelectedTicketLines]
134562
+ );
134563
+ const ticketsCount = usesTicketTypeSelections ? ticketTypeSelectionTotal : flowState.ticketsCount;
134564
+ const ticketSelections = useMemo(() => {
134565
+ if (waitlistClaim.claim) {
134566
+ return [{ ticketTypeId: null, quantity: 1 }];
134567
+ }
134568
+ if (usesTicketTypeSelections) {
134569
+ return normalizedSelectedTicketLines;
134570
+ }
134571
+ return [{ ticketTypeId: null, quantity: ticketsCount }];
134572
+ }, [normalizedSelectedTicketLines, ticketsCount, usesTicketTypeSelections, waitlistClaim.claim]);
134573
+ useEffect(() => {
134574
+ if (flowState.ticketsCount === ticketsCount) return;
134575
+ dispatchFlow({ type: "set-tickets-count", ticketsCount });
134576
+ }, [flowState.ticketsCount, ticketsCount]);
134331
134577
  const eligibilityResult = useEligibility({
134332
134578
  enabled: portalAuthState === "authenticated" || step === "enter-details" || step === "select-payment",
134333
134579
  occurrenceId,
134334
134580
  attendanceMode: selectedAttendanceMode,
134335
- ticketTypeId: selectedTicketTypeId,
134581
+ ticketSelections,
134336
134582
  selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
134337
- email: formData.email,
134338
- ticketsCount
134583
+ email: formData.email
134339
134584
  });
134340
134585
  const existingRegistrationResult = useExistingEventRegistration({
134341
134586
  isAuthenticated: portalAuthState === "authenticated",
@@ -134441,7 +134686,7 @@ function useEventRegistrationWizard(options) {
134441
134686
  setTicketsCount,
134442
134687
  setSelectedEvent,
134443
134688
  setSelectedAttendanceMode,
134444
- setSelectedTicketTypeId,
134689
+ setTicketSelections: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
134445
134690
  setPaymentSelection: (selection) => dispatchFlow({ type: "set-manual-payment-selection", selection }),
134446
134691
  setPendingAttendeeId,
134447
134692
  setStep,
@@ -134454,20 +134699,21 @@ function useEventRegistrationWizard(options) {
134454
134699
  }, [selectedEvent, preSelectedEvent]);
134455
134700
  const visibleTicketTypes = useMemo(
134456
134701
  () => getVisibleTicketTypes({
134457
- event: selectedEvent ?? preSelectedEvent,
134702
+ event: currentEvent,
134458
134703
  eligibility,
134459
134704
  selectedAttendanceMode
134460
134705
  }),
134461
- [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility]
134706
+ [currentEvent, selectedAttendanceMode, eligibility]
134462
134707
  );
134708
+ const singleSelectedTicketTypeId = getSingleSelectedTicketTypeId(normalizedSelectedTicketLines);
134463
134709
  const selectedTicketType = useMemo(
134464
134710
  () => findSelectedTicketType({
134465
- event: selectedEvent ?? preSelectedEvent,
134711
+ event: currentEvent,
134466
134712
  eligibility,
134467
- selectedTicketTypeId,
134713
+ selectedTicketTypeId: singleSelectedTicketTypeId,
134468
134714
  selectedAttendanceMode
134469
134715
  }),
134470
- [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
134716
+ [currentEvent, eligibility, selectedAttendanceMode, singleSelectedTicketTypeId]
134471
134717
  );
134472
134718
  const selectedSlidingAmount = useMemo(
134473
134719
  () => parseCurrencyInputToCents(selectedSlidingAmountInput),
@@ -134482,37 +134728,44 @@ function useEventRegistrationWizard(options) {
134482
134728
  selectedAmount: selectedSlidingAmount
134483
134729
  });
134484
134730
  const ticketTypeSelectionRequired = useMemo(() => {
134485
- const event = selectedEvent ?? preSelectedEvent;
134731
+ const event = currentEvent;
134486
134732
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
134487
134733
  return false;
134488
134734
  }
134489
134735
  if ((event.deliveryMode ?? "in_person") === "hybrid" && !selectedAttendanceMode) {
134490
134736
  return false;
134491
134737
  }
134492
- const selectedModeAvailability = selectedAttendanceMode ? (event.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
134493
- const canJoinWaitlist = (selectedModeAvailability?.availableSpots ?? event.availableSpots) === 0 && (selectedModeAvailability?.waitlistEnabled ?? event.waitlistEnabled) === true;
134738
+ const selectedModeAvailability2 = selectedAttendanceMode ? (event.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
134739
+ const canJoinWaitlist = (selectedModeAvailability2?.availableSpots ?? event.availableSpots) === 0 && (selectedModeAvailability2?.waitlistEnabled ?? event.waitlistEnabled) === true;
134494
134740
  return !canJoinWaitlist;
134495
- }, [selectedAttendanceMode, selectedEvent, preSelectedEvent]);
134496
- const hasValidSelectedTicketType = !ticketTypeSelectionRequired || Boolean(
134497
- selectedTicketType && selectedTicketType.availability.kind === "available"
134498
- );
134741
+ }, [currentEvent, selectedAttendanceMode]);
134742
+ const hasValidTicketSelection = !ticketTypeSelectionRequired || normalizedSelectedTicketLines.length > 0 && normalizedSelectedTicketLines.every((line) => {
134743
+ if (!line.ticketTypeId) return false;
134744
+ const ticketType = visibleTicketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null;
134745
+ return ticketType?.availability.kind === "available";
134746
+ });
134499
134747
  useEffect(() => {
134500
- const event = selectedEvent ?? preSelectedEvent;
134501
- if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
134502
- if (selectedTicketTypeId !== null) {
134503
- setSelectedTicketTypeId(null);
134748
+ if (!usesTicketTypeSelections) {
134749
+ if (selectedTicketLines.length > 0) {
134750
+ setSelectedTicketLines([]);
134504
134751
  }
134505
134752
  return;
134506
134753
  }
134507
- const implicitTicketType = getImplicitSingleAvailableTicketType(visibleTicketTypes);
134508
- if (implicitTicketType && selectedTicketTypeId !== implicitTicketType.id) {
134509
- setSelectedTicketTypeId(implicitTicketType.id);
134754
+ const availableTicketTypes = eventTicketTypesForSelection.filter(
134755
+ (ticketType) => ticketType.availability.kind === "available"
134756
+ );
134757
+ const implicitTicketType = getImplicitSingleAvailableTicketType(eventTicketTypesForSelection);
134758
+ if (implicitTicketType && normalizedSelectedTicketLines.length === 0) {
134759
+ setSelectedTicketLines([{ ticketTypeId: implicitTicketType.id, quantity: 1 }]);
134510
134760
  return;
134511
134761
  }
134512
- if (selectedTicketTypeId && !visibleTicketTypes.some((ticketType) => ticketType.id === selectedTicketTypeId)) {
134513
- setSelectedTicketTypeId(null);
134762
+ const filteredLines = normalizedSelectedTicketLines.filter(
134763
+ (line) => line.ticketTypeId && availableTicketTypes.some((ticketType) => ticketType.id === line.ticketTypeId)
134764
+ );
134765
+ if (filteredLines.length !== normalizedSelectedTicketLines.length) {
134766
+ setSelectedTicketLines(filteredLines);
134514
134767
  }
134515
- }, [selectedEvent, preSelectedEvent, selectedTicketTypeId, visibleTicketTypes]);
134768
+ }, [eventTicketTypesForSelection, normalizedSelectedTicketLines, selectedTicketLines.length, usesTicketTypeSelections]);
134516
134769
  useEffect(() => {
134517
134770
  if (!requireGuestDetails) {
134518
134771
  if (guests.length > 0) setGuests([]);
@@ -134556,21 +134809,20 @@ function useEventRegistrationWizard(options) {
134556
134809
  return resolveEventRequiresPayment({
134557
134810
  eligibility,
134558
134811
  selectedTicketType,
134559
- event: selectedEvent ?? preSelectedEvent
134812
+ event: currentEvent
134560
134813
  });
134561
134814
  },
134562
134815
  [
134563
134816
  eligibility,
134564
134817
  selectedTicketType,
134565
- selectedEvent,
134566
- preSelectedEvent,
134818
+ currentEvent,
134567
134819
  selectedEventSlidingScale,
134568
134820
  selectedSlidingAmount
134569
134821
  ]
134570
134822
  );
134571
134823
  const handleDetailsComplete = async () => {
134572
- if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
134573
- const message2 = "Select a ticket type to continue.";
134824
+ if (ticketTypeSelectionRequired && !hasValidTicketSelection) {
134825
+ const message2 = "Choose at least one available ticket to continue.";
134574
134826
  setErrorMessage(message2);
134575
134827
  setStep("error");
134576
134828
  return { ok: false, reason: "blocked", message: message2 };
@@ -134655,27 +134907,39 @@ function useEventRegistrationWizard(options) {
134655
134907
  const discountContext = useMemo(
134656
134908
  () => resolveDiscountContext({
134657
134909
  paymentSelection,
134658
- event: selectedEvent ?? preSelectedEvent,
134910
+ event: currentEvent,
134659
134911
  eventRequiresPayment,
134660
134912
  ticketsCount
134661
134913
  }),
134662
- [eventRequiresPayment, paymentSelection, selectedEvent, preSelectedEvent, ticketsCount]
134914
+ [currentEvent, eventRequiresPayment, paymentSelection, ticketsCount]
134915
+ );
134916
+ const fallbackUnitPriceCents = useMemo(
134917
+ () => currentEvent ? publicEventScalarPrice(resolveRuntimeEventPricing(currentEvent)) ?? 0 : 0,
134918
+ [currentEvent]
134919
+ );
134920
+ const ticketSelectionSubtotalCents = useMemo(() => {
134921
+ if (!usesTicketTypeSelections) {
134922
+ return Math.max(0, eligibility?.price ?? fallbackUnitPriceCents * ticketsCount);
134923
+ }
134924
+ return calculateTicketSelectionSubtotal({
134925
+ lines: normalizedSelectedTicketLines,
134926
+ ticketTypes: visibleTicketTypes,
134927
+ fallbackUnitPriceCents
134928
+ });
134929
+ }, [eligibility, fallbackUnitPriceCents, normalizedSelectedTicketLines, ticketsCount, usesTicketTypeSelections, visibleTicketTypes]);
134930
+ const ticketSelectionSummaryLines = useMemo(
134931
+ () => buildTicketSelectionSummaryLines({
134932
+ lines: ticketSelections,
134933
+ ticketTypes: visibleTicketTypes,
134934
+ fallbackLabel: "Ticket",
134935
+ fallbackUnitPriceCents
134936
+ }),
134937
+ [fallbackUnitPriceCents, ticketSelections, visibleTicketTypes]
134663
134938
  );
134664
134939
  const discountSubtotalCents = useMemo(() => {
134665
134940
  if (discountContext?.type !== "event") return void 0;
134666
- const event = selectedEvent ?? preSelectedEvent;
134667
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
134668
- return Math.max(0, eventPriceCents * ticketsCount);
134669
- }, [
134670
- discountContext,
134671
- selectedTicketType,
134672
- eligibility,
134673
- selectedEvent,
134674
- preSelectedEvent,
134675
- selectedEventSlidingScale,
134676
- selectedSlidingAmount,
134677
- ticketsCount
134678
- ]);
134941
+ return ticketSelectionSubtotalCents;
134942
+ }, [discountContext, ticketSelectionSubtotalCents]);
134679
134943
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
134680
134944
  const selectionMethod = paymentSelection.method;
134681
134945
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -134683,9 +134947,8 @@ function useEventRegistrationWizard(options) {
134683
134947
  api,
134684
134948
  registrationTarget,
134685
134949
  selectedAttendanceMode,
134686
- selectedTicketTypeId,
134950
+ ticketSelections,
134687
134951
  selectedSlidingAmount,
134688
- ticketsCount,
134689
134952
  formData,
134690
134953
  altchaSolution,
134691
134954
  requireGuestDetails,
@@ -134706,12 +134969,12 @@ function useEventRegistrationWizard(options) {
134706
134969
  setSelectedAttendanceMode(
134707
134970
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
134708
134971
  );
134709
- setSelectedTicketTypeId(null);
134972
+ setSelectedTicketLines([]);
134710
134973
  dispatchFlow({ type: "event-changed", hasPreSelectedContext: true });
134711
134974
  };
134712
134975
  const handleRetry = () => {
134713
134976
  dispatchFlow({ type: "reset", hasPreSelectedContext });
134714
- setSelectedTicketTypeId(null);
134977
+ setSelectedTicketLines([]);
134715
134978
  setSelectedSlidingAmountInput("");
134716
134979
  setSelectedAttendanceMode(
134717
134980
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
@@ -134747,17 +135010,20 @@ function useEventRegistrationWizard(options) {
134747
135010
  setTicketsCount,
134748
135011
  selectedAttendanceMode,
134749
135012
  setSelectedAttendanceMode,
134750
- selectedTicketTypeId,
134751
- setSelectedTicketTypeId,
135013
+ ticketSelections,
135014
+ selectedTicketLines: normalizedSelectedTicketLines,
135015
+ setSelectedTicketLines: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
134752
135016
  selectedSlidingAmountInput,
134753
135017
  setSelectedSlidingAmountInput,
134754
135018
  selectedSlidingAmount,
134755
135019
  selectedEventSlidingScale,
134756
135020
  slidingScaleSelectionMessage,
134757
135021
  selectedTicketType,
135022
+ ticketSelectionSummaryLines,
135023
+ ticketSelectionSubtotalCents,
134758
135024
  visibleTicketTypes,
134759
135025
  ticketTypeSelectionRequired,
134760
- hasValidSelectedTicketType,
135026
+ hasValidTicketSelection,
134761
135027
  eventRequiresPayment,
134762
135028
  formData,
134763
135029
  setFormData,