@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
@@ -23440,6 +23440,9 @@ function resolveDiscountContext(input) {
23440
23440
  if (!input.eventRequiresPayment) {
23441
23441
  return null;
23442
23442
  }
23443
+ if (input.ticketsCount <= 0) {
23444
+ return null;
23445
+ }
23443
23446
  if (selection?.method && selection.method !== "stripe") {
23444
23447
  return null;
23445
23448
  }
@@ -32914,6 +32917,7 @@ function transformEligibilityResponse(response) {
32914
32917
  requiresPayment: eligibility.requiresPayment,
32915
32918
  pricingMode: eligibility.pricingMode,
32916
32919
  selectedTicketTypeId: eligibility.selectedTicketTypeId ?? null,
32920
+ ticketLines: eligibility.ticketLines ?? [],
32917
32921
  ticketTypes: eligibility.ticketTypes ?? [],
32918
32922
  isFull: eligibility.isFull,
32919
32923
  waitlistEnabled: eligibility.waitlistEnabled,
@@ -32981,6 +32985,77 @@ var init_utils3 = __esm({
32981
32985
  }
32982
32986
  });
32983
32987
 
32988
+ // ../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts
32989
+ function normalizeTicketSelectionLines(lines) {
32990
+ const quantitiesByTicketType = /* @__PURE__ */ new Map();
32991
+ for (const line of lines) {
32992
+ const quantity = Math.floor(line.quantity);
32993
+ if (quantity <= 0) continue;
32994
+ const key = line.ticketTypeId ?? null;
32995
+ quantitiesByTicketType.set(key, (quantitiesByTicketType.get(key) ?? 0) + quantity);
32996
+ }
32997
+ return [...quantitiesByTicketType.entries()].map(([ticketTypeId, quantity]) => ({
32998
+ ticketTypeId,
32999
+ quantity
33000
+ }));
33001
+ }
33002
+ function getTicketSelectionTotal(lines) {
33003
+ return normalizeTicketSelectionLines(lines).reduce((total, line) => total + line.quantity, 0);
33004
+ }
33005
+ function getTicketLineQuantity(lines, ticketTypeId) {
33006
+ return normalizeTicketSelectionLines(lines).find((line) => line.ticketTypeId === ticketTypeId)?.quantity ?? 0;
33007
+ }
33008
+ function getSingleSelectedTicketTypeId(lines) {
33009
+ const normalized = normalizeTicketSelectionLines(lines);
33010
+ return normalized.length === 1 ? normalized[0]?.ticketTypeId ?? null : null;
33011
+ }
33012
+ function setTicketLineQuantity(input) {
33013
+ const nextQuantity = Math.max(0, Math.floor(input.quantity));
33014
+ const normalized = normalizeTicketSelectionLines(input.lines).filter((line) => line.ticketTypeId !== input.ticketTypeId);
33015
+ if (nextQuantity <= 0) {
33016
+ return normalized;
33017
+ }
33018
+ return [
33019
+ ...normalized,
33020
+ {
33021
+ ticketTypeId: input.ticketTypeId,
33022
+ quantity: nextQuantity
33023
+ }
33024
+ ];
33025
+ }
33026
+ function getTicketTypeLineMax(input) {
33027
+ const otherSelectedQuantity = input.totalSelectedQuantity - input.currentQuantity;
33028
+ const maxByBlock = input.maxTickets - otherSelectedQuantity;
33029
+ const maxByEvent = input.availableSpots == null ? input.maxTickets : input.availableSpots - otherSelectedQuantity;
33030
+ const maxByTicketType = input.ticketTypeRemainingCapacity ?? input.maxTickets;
33031
+ return Math.max(0, Math.min(maxByBlock, maxByEvent, maxByTicketType));
33032
+ }
33033
+ function calculateTicketSelectionSubtotal(input) {
33034
+ return normalizeTicketSelectionLines(input.lines).reduce((subtotal, line) => {
33035
+ const ticketType = line.ticketTypeId ? input.ticketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null : null;
33036
+ const unitPriceCents = ticketType?.priceAmount ?? input.fallbackUnitPriceCents;
33037
+ return subtotal + Math.max(0, unitPriceCents) * line.quantity;
33038
+ }, 0);
33039
+ }
33040
+ function buildTicketSelectionSummaryLines(input) {
33041
+ return normalizeTicketSelectionLines(input.lines).map((line) => {
33042
+ const ticketType = line.ticketTypeId ? input.ticketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null : null;
33043
+ const unitPriceCents = ticketType?.priceAmount ?? input.fallbackUnitPriceCents;
33044
+ return {
33045
+ ticketTypeId: line.ticketTypeId,
33046
+ label: ticketType?.name ?? input.fallbackLabel,
33047
+ quantity: line.quantity,
33048
+ unitPriceCents,
33049
+ subtotalCents: Math.max(0, unitPriceCents) * line.quantity
33050
+ };
33051
+ });
33052
+ }
33053
+ var init_ticketSelectionLines = __esm({
33054
+ "../blocks/src/system/runtime/nodes/event-registration/ticketSelectionLines.ts"() {
33055
+ "use strict";
33056
+ }
33057
+ });
33058
+
32984
33059
  // ../blocks/src/system/runtime/nodes/event-registration/hooks.state.ts
32985
33060
  function eligibilityLookupReducer(state, action) {
32986
33061
  switch (action.type) {
@@ -33095,6 +33170,7 @@ function usePortalAuth() {
33095
33170
  }
33096
33171
  function useEligibility(params) {
33097
33172
  const api = useBlockApi();
33173
+ const ticketSelectionsKey = JSON.stringify(normalizeTicketSelectionLines(params.ticketSelections));
33098
33174
  const [state, dispatch] = useReducer7(
33099
33175
  eligibilityLookupReducer,
33100
33176
  initialEligibilityLookupState
@@ -33108,7 +33184,8 @@ function useEligibility(params) {
33108
33184
  dispatch({ type: "reset" });
33109
33185
  return;
33110
33186
  }
33111
- if (!params.occurrenceId || !params.email.trim()) {
33187
+ const ticketSelections = JSON.parse(ticketSelectionsKey);
33188
+ if (!params.occurrenceId || !params.email.trim() || ticketSelections.length === 0) {
33112
33189
  requestIdRef.current += 1;
33113
33190
  dispatch({ type: "reset" });
33114
33191
  return;
@@ -33120,10 +33197,9 @@ function useEligibility(params) {
33120
33197
  const result = await api.checkEligibility({
33121
33198
  occurrenceId: params.occurrenceId,
33122
33199
  attendanceMode: params.attendanceMode ?? void 0,
33123
- ticketTypeId: params.ticketTypeId ?? void 0,
33200
+ ticketSelections,
33124
33201
  selectedSlidingAmount: params.selectedSlidingAmount ?? void 0,
33125
- email: params.email.trim().toLowerCase(),
33126
- ticketsCount: params.ticketsCount
33202
+ email: params.email.trim().toLowerCase()
33127
33203
  });
33128
33204
  if (cancelled || requestIdRef.current !== requestId) return;
33129
33205
  dispatch({
@@ -33142,7 +33218,15 @@ function useEligibility(params) {
33142
33218
  return () => {
33143
33219
  cancelled = true;
33144
33220
  };
33145
- }, [api, params.enabled, params.occurrenceId, params.attendanceMode, params.ticketTypeId, params.selectedSlidingAmount, params.email, params.ticketsCount]);
33221
+ }, [
33222
+ api,
33223
+ params.enabled,
33224
+ params.occurrenceId,
33225
+ params.attendanceMode,
33226
+ ticketSelectionsKey,
33227
+ params.selectedSlidingAmount,
33228
+ params.email
33229
+ ]);
33146
33230
  return {
33147
33231
  state,
33148
33232
  eligibility: state.eligibility,
@@ -33199,6 +33283,7 @@ var init_hooks = __esm({
33199
33283
  "use strict";
33200
33284
  init_api();
33201
33285
  init_utils3();
33286
+ init_ticketSelectionLines();
33202
33287
  init_hooks_state();
33203
33288
  }
33204
33289
  });
@@ -33224,9 +33309,15 @@ function savePendingRegistration(siteId, occurrenceId, data) {
33224
33309
  }
33225
33310
  try {
33226
33311
  const key = getStorageKey(siteId, occurrenceId);
33312
+ const ticketSelections = normalizeTicketSelectionLines(data.ticketSelections);
33313
+ if (ticketSelections.length === 0) {
33314
+ console.warn("[sessionStorage] No ticket selections to save, skipping registration state");
33315
+ return;
33316
+ }
33227
33317
  const entry = {
33228
33318
  ...data,
33229
- version: 3,
33319
+ ticketSelections,
33320
+ version: 4,
33230
33321
  timestamp: Date.now()
33231
33322
  };
33232
33323
  sessionStorage.setItem(key, JSON.stringify(entry));
@@ -33272,8 +33363,8 @@ function parseAndValidateEntry(stored, key) {
33272
33363
  return null;
33273
33364
  }
33274
33365
  const version = raw.version;
33275
- if (version !== 1 && version !== 2 && version !== 3) {
33276
- console.warn("[sessionStorage] Unknown version, ignoring entry");
33366
+ if (version !== 4) {
33367
+ console.warn("[sessionStorage] Outdated version, ignoring entry");
33277
33368
  sessionStorage.removeItem(key);
33278
33369
  return null;
33279
33370
  }
@@ -33289,34 +33380,12 @@ function parseAndValidateEntry(stored, key) {
33289
33380
  sessionStorage.removeItem(key);
33290
33381
  return null;
33291
33382
  }
33292
- if (entry.version === 1) {
33293
- const legacy = entry;
33294
- return {
33295
- version: 3,
33296
- attendeeId: legacy.attendeeId,
33297
- formData: legacy.formData,
33298
- ticketsCount: legacy.ticketsCount,
33299
- selectedEventId: legacy.selectedEventId,
33300
- selectedAttendanceMode: null,
33301
- selectedTicketTypeId: null,
33302
- paymentSelection: { method: legacy.paymentMethod },
33303
- timestamp: legacy.timestamp
33304
- };
33305
- }
33306
- if (entry.version === 2) {
33307
- return {
33308
- version: 3,
33309
- attendeeId: entry.attendeeId,
33310
- formData: entry.formData,
33311
- ticketsCount: entry.ticketsCount,
33312
- selectedEventId: entry.selectedEventId,
33313
- selectedAttendanceMode: null,
33314
- selectedTicketTypeId: null,
33315
- paymentSelection: entry.paymentSelection,
33316
- timestamp: entry.timestamp
33317
- };
33383
+ const ticketSelections = normalizeTicketSelectionLines(entry.ticketSelections);
33384
+ if (ticketSelections.length === 0 || getTicketSelectionTotal(ticketSelections) <= 0) {
33385
+ sessionStorage.removeItem(key);
33386
+ return null;
33318
33387
  }
33319
- return entry;
33388
+ return { ...entry, ticketSelections };
33320
33389
  } catch {
33321
33390
  console.warn("[sessionStorage] Failed to parse entry, removing");
33322
33391
  sessionStorage.removeItem(key);
@@ -33348,8 +33417,9 @@ function cleanupStaleRegistrations() {
33348
33417
  const stored = sessionStorage.getItem(key);
33349
33418
  if (!stored) continue;
33350
33419
  const entry = JSON.parse(stored);
33351
- const age = Date.now() - entry.timestamp;
33352
- if (age > STALE_THRESHOLD_MS) {
33420
+ const timestamp = typeof entry.timestamp === "number" ? entry.timestamp : NaN;
33421
+ const age = Date.now() - timestamp;
33422
+ if (!Number.isFinite(timestamp) || age > STALE_THRESHOLD_MS) {
33353
33423
  keysToRemove.push(key);
33354
33424
  }
33355
33425
  } catch {
@@ -33370,6 +33440,7 @@ var STORAGE_PREFIX, STALE_THRESHOLD_MS;
33370
33440
  var init_sessionStorage = __esm({
33371
33441
  "../blocks/src/system/runtime/nodes/event-registration/sessionStorage.ts"() {
33372
33442
  "use strict";
33443
+ init_ticketSelectionLines();
33373
33444
  STORAGE_PREFIX = "riverbank:event-registration:";
33374
33445
  STALE_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
33375
33446
  }
@@ -33459,14 +33530,16 @@ function getEventAttendanceModeLabel(mode) {
33459
33530
  }
33460
33531
  }
33461
33532
  function TicketCounter(props2) {
33462
- const { ticketsCount, maxAllowed, onTicketsChange } = props2;
33533
+ const { ticketsCount, maxAllowed, label, onTicketsChange } = props2;
33534
+ const minAllowed = props2.minAllowed ?? 1;
33463
33535
  return /* @__PURE__ */ jsxs54("div", { className: "er-row er-counter", children: [
33464
33536
  /* @__PURE__ */ jsx77(
33465
33537
  "button",
33466
33538
  {
33467
33539
  type: "button",
33468
- onClick: () => onTicketsChange(Math.max(1, ticketsCount - 1)),
33469
- disabled: ticketsCount <= 1,
33540
+ "aria-label": `Decrease ${label}`,
33541
+ onClick: () => onTicketsChange(Math.max(minAllowed, ticketsCount - 1)),
33542
+ disabled: ticketsCount <= minAllowed,
33470
33543
  className: "er-counter-btn",
33471
33544
  children: "-"
33472
33545
  }
@@ -33476,6 +33549,7 @@ function TicketCounter(props2) {
33476
33549
  "button",
33477
33550
  {
33478
33551
  type: "button",
33552
+ "aria-label": `Increase ${label}`,
33479
33553
  onClick: () => onTicketsChange(Math.min(maxAllowed, ticketsCount + 1)),
33480
33554
  disabled: ticketsCount >= maxAllowed,
33481
33555
  className: "er-counter-btn",
@@ -33528,14 +33602,86 @@ function SlidingScaleAmountField(props2) {
33528
33602
  props2.message ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: props2.message }) : null
33529
33603
  ] });
33530
33604
  }
33531
- function getTicketTypeCounterAvailabilityLabel(input) {
33532
- if (input.selectedRemainingCapacity !== null) {
33533
- return `${input.selectedRemainingCapacity} remaining for this ticket type`;
33534
- }
33535
- if (input.eventAvailableSpots != null) {
33536
- return `${input.eventAvailableSpots} spots available`;
33537
- }
33538
- return null;
33605
+ function TicketTypeQuantityRows(props2) {
33606
+ const totalSelectedQuantity = getTicketSelectionTotal(props2.ticketSelections);
33607
+ const selectedTicketTypes = props2.ticketTypes.filter(
33608
+ (ticketType) => getTicketLineQuantity(props2.ticketSelections, ticketType.id) > 0
33609
+ );
33610
+ return /* @__PURE__ */ jsxs54("div", { className: "er-ticket-lines", children: [
33611
+ /* @__PURE__ */ jsx77("div", { className: "er-ticket-line-list", role: "list", "aria-label": "Tickets", children: props2.ticketTypes.map((ticketType) => {
33612
+ const quantity = getTicketLineQuantity(props2.ticketSelections, ticketType.id);
33613
+ const disabled = ticketType.availability.kind === "unavailable";
33614
+ const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
33615
+ const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, props2.currency) : "Free";
33616
+ const maxAllowed = disabled ? 0 : getTicketTypeLineMax({
33617
+ currentQuantity: quantity,
33618
+ totalSelectedQuantity,
33619
+ maxTickets: props2.maxTickets,
33620
+ availableSpots: props2.availableSpots,
33621
+ ticketTypeRemainingCapacity: ticketType.availability.remainingCapacity
33622
+ });
33623
+ return /* @__PURE__ */ jsxs54(
33624
+ "div",
33625
+ {
33626
+ className: `er-ticket-line${disabled ? " er-ticket-line--disabled" : ""}`,
33627
+ role: "listitem",
33628
+ children: [
33629
+ /* @__PURE__ */ jsxs54("div", { className: "er-ticket-line-main", children: [
33630
+ /* @__PURE__ */ jsxs54("div", { className: "er-ticket-line-heading", children: [
33631
+ /* @__PURE__ */ jsx77("span", { className: "er-ticket-line-name", children: ticketType.name }),
33632
+ /* @__PURE__ */ jsx77("span", { className: "er-ticket-line-price", children: priceLabel })
33633
+ ] }),
33634
+ ticketType.description ? /* @__PURE__ */ jsx77("p", { className: "er-ticket-line-detail", children: ticketType.description }) : null,
33635
+ availabilityLabel ? /* @__PURE__ */ jsx77("p", { className: "er-ticket-line-detail", children: availabilityLabel }) : ticketType.availability.remainingCapacity !== null ? /* @__PURE__ */ jsxs54("p", { className: "er-ticket-line-detail", children: [
33636
+ ticketType.availability.remainingCapacity,
33637
+ " remaining for this ticket type"
33638
+ ] }) : null
33639
+ ] }),
33640
+ /* @__PURE__ */ jsx77(
33641
+ TicketCounter,
33642
+ {
33643
+ ticketsCount: quantity,
33644
+ minAllowed: 0,
33645
+ maxAllowed,
33646
+ label: ticketType.name,
33647
+ onTicketsChange: (nextQuantity) => {
33648
+ props2.onTicketSelectionsChange?.(setTicketLineQuantity({
33649
+ lines: props2.ticketSelections,
33650
+ ticketTypeId: ticketType.id,
33651
+ quantity: nextQuantity
33652
+ }));
33653
+ }
33654
+ }
33655
+ )
33656
+ ]
33657
+ },
33658
+ ticketType.id
33659
+ );
33660
+ }) }),
33661
+ /* @__PURE__ */ jsx77(
33662
+ TicketCounterLimit,
33663
+ {
33664
+ maximumLabel: `${props2.maxTickets} tickets maximum`,
33665
+ availabilityLabel: props2.availableSpots != null ? `${props2.availableSpots} spots available` : null
33666
+ }
33667
+ ),
33668
+ selectedTicketTypes.length > 0 ? /* @__PURE__ */ jsxs54("div", { className: "er-ticket-line-summary", "aria-live": "polite", children: [
33669
+ /* @__PURE__ */ jsxs54("div", { className: "er-ticket-line-summary-title", children: [
33670
+ totalSelectedQuantity,
33671
+ " ticket",
33672
+ totalSelectedQuantity === 1 ? "" : "s",
33673
+ " selected"
33674
+ ] }),
33675
+ /* @__PURE__ */ jsx77("ul", { className: "er-ticket-line-summary-list", children: selectedTicketTypes.map((ticketType) => {
33676
+ const quantity = getTicketLineQuantity(props2.ticketSelections, ticketType.id);
33677
+ return /* @__PURE__ */ jsxs54("li", { children: [
33678
+ quantity,
33679
+ " x ",
33680
+ ticketType.name
33681
+ ] }, ticketType.id);
33682
+ }) })
33683
+ ] }) : props2.hasAvailableTicketTypes ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "Choose at least one ticket to continue." }) : /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "No ticket types are currently available." })
33684
+ ] });
33539
33685
  }
33540
33686
  function getAttendanceModeDetail(attendanceMode) {
33541
33687
  if (attendanceMode.availableSpots === null) {
@@ -33553,6 +33699,7 @@ function AttendanceModeTiles(props2) {
33553
33699
  "button",
33554
33700
  {
33555
33701
  type: "button",
33702
+ "aria-pressed": isSelected,
33556
33703
  className: `er-payment-tile${isSelected ? " er-payment-tile--selected" : ""}`,
33557
33704
  onClick: () => props2.onAttendanceModeChange?.(attendanceMode.mode),
33558
33705
  children: [
@@ -33590,6 +33737,7 @@ var init_TicketSelectionStep = __esm({
33590
33737
  init_src();
33591
33738
  init_pricing2();
33592
33739
  init_utils3();
33740
+ init_ticketSelectionLines();
33593
33741
  TicketSelectionStep = ({
33594
33742
  event,
33595
33743
  occurrenceContext: _occurrenceContext,
@@ -33597,8 +33745,9 @@ var init_TicketSelectionStep = __esm({
33597
33745
  onAttendanceModeChange,
33598
33746
  ticketsCount,
33599
33747
  maxTickets,
33600
- selectedTicketTypeId = null,
33601
- onTicketTypeChange,
33748
+ visibleTicketTypes: visibleTicketTypesOverride,
33749
+ ticketSelections = [],
33750
+ onTicketSelectionsChange,
33602
33751
  slidingScale = null,
33603
33752
  selectedSlidingAmountInput = "",
33604
33753
  slidingScaleSelectionMessage = null,
@@ -33616,28 +33765,22 @@ var init_TicketSelectionStep = __esm({
33616
33765
  const eventPricingMode = eventPricing ? publicEventPricingMode(eventPricing) : void 0;
33617
33766
  const eventPricingCurrency = eventPricing ? publicEventPricingCurrency(eventPricing) : null;
33618
33767
  const usesTicketTypes = eventPricingMode === "ticket_types" && !canJoinWaitlist;
33619
- const visibleTicketTypes = usesTicketTypes ? getVisibleTicketTypes({
33768
+ const visibleTicketTypes = usesTicketTypes ? visibleTicketTypesOverride ?? getVisibleTicketTypes({
33620
33769
  event,
33621
33770
  eligibility: null,
33622
33771
  selectedAttendanceMode
33623
33772
  }) : [];
33624
- const selectedTicketType = usesTicketTypes ? visibleTicketTypes.find((ticketType) => ticketType.id === selectedTicketTypeId) ?? null : null;
33625
33773
  const implicitTicketType = usesTicketTypes ? getImplicitSingleAvailableTicketType(visibleTicketTypes) : null;
33626
- const effectiveSelectedTicketType = selectedTicketType ?? implicitTicketType;
33627
- const shouldShowTicketTypeTiles = usesTicketTypes && implicitTicketType === null;
33628
- const selectedRemainingCapacity = effectiveSelectedTicketType?.availability.remainingCapacity ?? null;
33629
- const noSellableTicketTypes = usesTicketTypes && (visibleTicketTypes.length === 0 || visibleTicketTypes.every((ticketType) => ticketType.availability.kind === "unavailable"));
33774
+ const noVisibleTicketTypes = usesTicketTypes && visibleTicketTypes.length === 0;
33775
+ const hasAvailableTicketTypes = usesTicketTypes && visibleTicketTypes.some((ticketType) => ticketType.availability.kind === "available");
33776
+ const aggregateRemainingCapacity = implicitTicketType?.availability.remainingCapacity ?? null;
33630
33777
  const maxAllowed = getMaxAllowedTicketCount({
33631
33778
  canJoinWaitlist,
33632
33779
  usesTicketTypes,
33633
33780
  maxTickets,
33634
33781
  availableSpots,
33635
- selectedTicketTypeRemainingCapacity: selectedRemainingCapacity,
33636
- hasSelectedTicketType: effectiveSelectedTicketType !== null
33637
- });
33638
- const selectedTicketTypeAvailabilityLabel = getTicketTypeCounterAvailabilityLabel({
33639
- selectedRemainingCapacity,
33640
- eventAvailableSpots: event?.availableSpots
33782
+ selectedTicketTypeRemainingCapacity: aggregateRemainingCapacity,
33783
+ hasSelectedTicketType: implicitTicketType !== null
33641
33784
  });
33642
33785
  const eventAvailabilityLabel = !canJoinWaitlist && event?.availableSpots != null ? `${event.availableSpots} spots available` : null;
33643
33786
  if (deliveryMode === "hybrid" && !selectedAttendanceMode) {
@@ -33654,7 +33797,7 @@ var init_TicketSelectionStep = __esm({
33654
33797
  /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "Choose whether you want to attend in person or online." })
33655
33798
  ] });
33656
33799
  }
33657
- if (isSoldOut && !canJoinWaitlist && !usesTicketTypes) {
33800
+ if (isSoldOut && !canJoinWaitlist) {
33658
33801
  return /* @__PURE__ */ jsxs54("div", { className: "er-stack", children: [
33659
33802
  /* @__PURE__ */ jsx77("h3", { className: "er-title", children: "Sold Out" }),
33660
33803
  /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "Sorry you missed out! This event is fully booked." })
@@ -33672,52 +33815,21 @@ var init_TicketSelectionStep = __esm({
33672
33815
  }
33673
33816
  )
33674
33817
  ] }) : null,
33675
- usesTicketTypes ? /* @__PURE__ */ jsxs54(Fragment12, { children: [
33676
- shouldShowTicketTypeTiles ? /* @__PURE__ */ jsxs54(Fragment12, { children: [
33677
- /* @__PURE__ */ jsx77("h3", { className: "er-title", children: "Choose a ticket type" }),
33678
- /* @__PURE__ */ jsx77("div", { className: "er-payment-tiles", role: "list", "aria-label": "Ticket types", children: visibleTicketTypes.map((ticketType) => {
33679
- const availabilityLabel = getTicketTypeAvailabilityLabel(ticketType);
33680
- const disabled = ticketType.availability.kind === "unavailable";
33681
- const isSelected = selectedTicketTypeId === ticketType.id;
33682
- const priceLabel = ticketType.priceAmount > 0 ? formatPrice(ticketType.priceAmount, eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2) : "Free";
33683
- return /* @__PURE__ */ jsx77("div", { role: "listitem", children: /* @__PURE__ */ jsxs54(
33684
- "button",
33685
- {
33686
- type: "button",
33687
- className: `er-payment-tile${isSelected && !disabled ? " er-payment-tile--selected" : ""}${disabled ? " er-payment-tile--disabled" : ""}`,
33688
- disabled,
33689
- onClick: () => onTicketTypeChange?.(ticketType.id),
33690
- children: [
33691
- /* @__PURE__ */ jsx77("div", { className: "er-payment-tile-label", children: ticketType.name }),
33692
- /* @__PURE__ */ jsx77("div", { className: "er-payment-tile-detail", children: priceLabel }),
33693
- ticketType.description ? /* @__PURE__ */ jsx77("div", { className: "er-payment-tile-detail", children: ticketType.description }) : null,
33694
- availabilityLabel ? /* @__PURE__ */ jsx77("div", { className: "er-payment-tile-detail", children: availabilityLabel }) : null
33695
- ]
33696
- }
33697
- ) }, ticketType.id);
33698
- }) })
33699
- ] }) : null,
33700
- noSellableTicketTypes ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "No ticket types are currently available." }) : !effectiveSelectedTicketType ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "Select one ticket type to continue." }) : effectiveSelectedTicketType.availability.kind === "unavailable" ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: getTicketTypeAvailabilityLabel(effectiveSelectedTicketType) ?? "This ticket type is unavailable." }) : /* @__PURE__ */ jsxs54(Fragment12, { children: [
33701
- /* @__PURE__ */ jsx77("h3", { className: "er-title", children: "How many tickets?" }),
33702
- /* @__PURE__ */ jsxs54("div", { className: "er-ticket-counter-group", children: [
33703
- /* @__PURE__ */ jsx77(
33704
- TicketCounter,
33705
- {
33706
- ticketsCount,
33707
- maxAllowed,
33708
- onTicketsChange
33709
- }
33710
- ),
33711
- /* @__PURE__ */ jsx77(
33712
- TicketCounterLimit,
33713
- {
33714
- maximumLabel: `${maxAllowed} tickets maximum`,
33715
- availabilityLabel: selectedTicketTypeAvailabilityLabel
33716
- }
33717
- )
33718
- ] })
33719
- ] })
33720
- ] }) : /* @__PURE__ */ jsxs54(Fragment12, { children: [
33818
+ usesTicketTypes ? /* @__PURE__ */ jsx77(Fragment12, { children: noVisibleTicketTypes ? /* @__PURE__ */ jsx77("p", { className: "er-text er-text--muted", children: "No ticket types are currently available." }) : /* @__PURE__ */ jsxs54(Fragment12, { children: [
33819
+ /* @__PURE__ */ jsx77("h3", { className: "er-title", children: "Choose tickets" }),
33820
+ /* @__PURE__ */ jsx77(
33821
+ TicketTypeQuantityRows,
33822
+ {
33823
+ ticketTypes: visibleTicketTypes,
33824
+ ticketSelections,
33825
+ maxTickets,
33826
+ availableSpots,
33827
+ currency: eventPricingCurrency ?? DEFAULT_EVENT_CURRENCY2,
33828
+ hasAvailableTicketTypes,
33829
+ onTicketSelectionsChange
33830
+ }
33831
+ )
33832
+ ] }) }) : /* @__PURE__ */ jsxs54(Fragment12, { children: [
33721
33833
  slidingScale ? /* @__PURE__ */ jsx77(
33722
33834
  SlidingScaleAmountField,
33723
33835
  {
@@ -33735,6 +33847,7 @@ var init_TicketSelectionStep = __esm({
33735
33847
  {
33736
33848
  ticketsCount,
33737
33849
  maxAllowed,
33850
+ label: canJoinWaitlist ? "waitlist spots" : "tickets",
33738
33851
  onTicketsChange
33739
33852
  }
33740
33853
  ),
@@ -34620,7 +34733,7 @@ var init_PaymentSelectionStep = __esm({
34620
34733
  }) => {
34621
34734
  const occurrenceId = occurrenceContext?.id ?? event?.registrationOccurrenceId;
34622
34735
  const selectedMethod = selectedSelection.method;
34623
- const totalPrice = eligibility?.price ? eligibility.price * ticketsCount : 0;
34736
+ const totalPrice = eligibility?.price ?? 0;
34624
34737
  const canShowDirectPaymentOptions = Boolean(
34625
34738
  eligibility?.eligible && eligibility.canPayDirectly && eligibility.price
34626
34739
  );
@@ -35102,8 +35215,9 @@ function EventRegistrationWizardContent(props2) {
35102
35215
  onAttendanceModeChange: wizard.setSelectedAttendanceMode,
35103
35216
  ticketsCount: wizard.ticketsCount,
35104
35217
  maxTickets: wizard.maxTicketsNum,
35105
- selectedTicketTypeId: wizard.selectedTicketTypeId,
35106
- onTicketTypeChange: wizard.setSelectedTicketTypeId,
35218
+ visibleTicketTypes: wizard.eligibility ? wizard.visibleTicketTypes : void 0,
35219
+ ticketSelections: wizard.selectedTicketLines,
35220
+ onTicketSelectionsChange: wizard.setSelectedTicketLines,
35107
35221
  slidingScale: wizard.selectedEventSlidingScale,
35108
35222
  selectedSlidingAmountInput: wizard.selectedSlidingAmountInput,
35109
35223
  slidingScaleSelectionMessage: wizard.slidingScaleSelectionMessage,
@@ -35224,9 +35338,9 @@ function EventRegistrationWizardActions(props2) {
35224
35338
  const isWaitingForEligibility = wizard.portalAuthState === "authenticated" && wizard.eligibilityLoading;
35225
35339
  const isCheckingExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistrationLoading;
35226
35340
  const hasExistingRegistration = wizard.portalAuthState === "authenticated" && wizard.existingRegistration !== null;
35227
- const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidSelectedTicketType;
35341
+ const ticketTypeSelectionBlocked = wizard.ticketTypeSelectionRequired && !wizard.hasValidTicketSelection;
35228
35342
  const isContinueDisabled = isWaitingForEligibility || isCheckingExistingRegistration || hasExistingRegistration || isSoldOutWithoutWaitlist || ticketTypeSelectionBlocked;
35229
- const continueLabel = isCheckingExistingRegistration ? "Checking registration..." : hasExistingRegistration ? "Already registered" : isSoldOutWithoutWaitlist ? "Sold out" : ticketTypeSelectionBlocked ? "Choose a ticket type" : "Continue";
35343
+ const continueLabel = isCheckingExistingRegistration ? "Checking registration..." : hasExistingRegistration ? "Already registered" : isSoldOutWithoutWaitlist ? "Sold out" : ticketTypeSelectionBlocked ? "Choose a ticket" : "Continue";
35230
35344
  return /* @__PURE__ */ jsxs64("div", { className: "er-actions", children: [
35231
35345
  !wizard.hasPreSelectedContext && /* @__PURE__ */ jsx89("button", { type: "button", onClick: () => wizard.setStep("select-event"), className: "er-back-btn", children: "\u2190 Back" }),
35232
35346
  /* @__PURE__ */ jsx89(
@@ -35324,13 +35438,17 @@ function EventRegistrationSummaryBar(props2) {
35324
35438
  const summaryTimeZone = wizard.occurrenceContext?.timeZone ?? summaryEvent?.timeZone ?? null;
35325
35439
  const occurrenceOverrideTitle = wizard.occurrenceContext?.overrides?.title;
35326
35440
  const summaryTitle = occurrenceOverrideTitle ?? summaryEvent?.title ?? "";
35327
- const summaryPrice = wizard.selectedTicketType?.priceAmount ?? wizard.eligibility?.price ?? (wizard.selectedEventSlidingScale ? wizard.selectedSlidingAmount : null) ?? (summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0;
35328
35441
  const summaryCurrency = (summaryEvent ? publicEventPricingCurrency(summaryEvent.pricing) : null) ?? DEFAULT_EVENT_CURRENCY2;
35329
35442
  const teamMemberNames = (summaryEvent?.teamMembers ?? []).map((teamMember) => teamMember.name).filter(Boolean);
35330
- const subtotalCents = Math.max(0, summaryPrice * wizard.ticketsCount);
35443
+ const fallbackSubtotalCents = Math.max(
35444
+ 0,
35445
+ ((summaryEvent ? publicEventScalarPrice(summaryEvent.pricing) : null) ?? 0) * wizard.ticketsCount
35446
+ );
35447
+ const subtotalCents = Math.max(0, wizard.ticketSelectionSubtotalCents ?? fallbackSubtotalCents);
35331
35448
  const autoAppliedDiscount = wizard.discountCode.trim().length > 0 ? null : wizard.autoAppliedDiscount;
35332
35449
  const autoAppliedDiscountCents = Math.max(0, autoAppliedDiscount?.computedAmountCents ?? 0);
35333
35450
  const discountedTotalCents = Math.max(0, subtotalCents - autoAppliedDiscountCents);
35451
+ const selectedLineSummary = (wizard.ticketSelectionSummaryLines ?? []).filter((line) => line.ticketTypeId !== null).map((line) => `${line.quantity} x ${line.label}`).join(", ");
35334
35452
  const selectedDirectPaymentOption = getSelectedDirectPaymentOption({
35335
35453
  wizard,
35336
35454
  totalCents: discountedTotalCents,
@@ -35367,9 +35485,9 @@ function EventRegistrationSummaryBar(props2) {
35367
35485
  /* @__PURE__ */ jsx90("h4", { className: "er-summary-bar-event", children: summaryTitle }),
35368
35486
  /* @__PURE__ */ jsxs65("span", { className: "er-summary-bar-meta", children: [
35369
35487
  summaryDate && formatEventDateTime(summaryDate, { timeZone: summaryTimeZone }),
35370
- wizard.selectedTicketType ? /* @__PURE__ */ jsxs65(Fragment16, { children: [
35488
+ selectedLineSummary ? /* @__PURE__ */ jsxs65(Fragment16, { children: [
35371
35489
  " \xB7 ",
35372
- wizard.selectedTicketType.name
35490
+ selectedLineSummary
35373
35491
  ] }) : null,
35374
35492
  wizard.step !== "select-event" && wizard.step !== "select-tickets" && wizard.ticketsCount > 0 && /* @__PURE__ */ jsxs65(Fragment16, { children: [
35375
35493
  " \xB7 ",
@@ -35383,8 +35501,8 @@ function EventRegistrationSummaryBar(props2) {
35383
35501
  teamMemberNames.join(", ")
35384
35502
  ] })
35385
35503
  ] }),
35386
- summaryPrice > 0 && /* @__PURE__ */ jsx90("div", { className: "er-summary-bar-pricing", children: productMethod ? /* @__PURE__ */ jsxs65(Fragment16, { children: [
35387
- /* @__PURE__ */ jsx90("span", { className: "er-summary-bar-price er-summary-bar-price--struck", children: formatPrice(summaryPrice * wizard.ticketsCount, summaryCurrency) }),
35504
+ subtotalCents > 0 && /* @__PURE__ */ jsx90("div", { className: "er-summary-bar-pricing", children: productMethod ? /* @__PURE__ */ jsxs65(Fragment16, { children: [
35505
+ /* @__PURE__ */ jsx90("span", { className: "er-summary-bar-price er-summary-bar-price--struck", children: formatPrice(subtotalCents, summaryCurrency) }),
35388
35506
  /* @__PURE__ */ jsx90("div", { className: "er-summary-bar-included", children: wizard.paymentSelection.method === "pass" || wizard.paymentSelection.method === "pass_bundle" ? "1 pass credit" : "Included in membership" })
35389
35507
  ] }) : flexibleBalanceSummary ? /* @__PURE__ */ jsxs65(Fragment16, { children: [
35390
35508
  flexibleBalanceSummary.total ? /* @__PURE__ */ jsxs65("div", { className: "er-summary-bar-product-detail", children: [
@@ -35660,7 +35778,7 @@ function useEventRegistrationStripeReturn(options) {
35660
35778
  setRegistrationResult,
35661
35779
  setSelectedEvent,
35662
35780
  setSelectedAttendanceMode,
35663
- setSelectedTicketTypeId,
35781
+ setTicketSelections,
35664
35782
  setStep,
35665
35783
  setTicketsCount,
35666
35784
  step
@@ -35679,13 +35797,13 @@ function useEventRegistrationStripeReturn(options) {
35679
35797
  const pending = getPendingRegistration(siteId, occurrenceId || void 0);
35680
35798
  if (!pending) return;
35681
35799
  setFormData(pending.formData);
35682
- setTicketsCount(pending.ticketsCount);
35800
+ setTicketSelections(pending.ticketSelections);
35801
+ setTicketsCount(getTicketSelectionTotal(pending.ticketSelections));
35683
35802
  if (pending.selectedEventId) {
35684
35803
  const event = events.find((e) => e.publicEventId === pending.selectedEventId) ?? null;
35685
35804
  if (event) setSelectedEvent(event);
35686
35805
  }
35687
35806
  setSelectedAttendanceMode(pending.selectedAttendanceMode ?? null);
35688
- setSelectedTicketTypeId(pending.selectedTicketTypeId ?? null);
35689
35807
  setPaymentSelection(pending.paymentSelection);
35690
35808
  if (registrationStatus === "cancelled") {
35691
35809
  setStep("select-payment");
@@ -35730,6 +35848,7 @@ var init_useEventRegistrationStripeReturn = __esm({
35730
35848
  init_usePaymentVerification();
35731
35849
  init_shared6();
35732
35850
  init_sessionStorage();
35851
+ init_ticketSelectionLines();
35733
35852
  }
35734
35853
  });
35735
35854
 
@@ -35746,6 +35865,10 @@ function useEventRegistrationSubmit(options) {
35746
35865
  }
35747
35866
  options.setStep("submitting");
35748
35867
  try {
35868
+ const ticketSelections = normalizeTicketSelectionLines(options.ticketSelections);
35869
+ if (ticketSelections.length === 0) {
35870
+ throw new Error("Choose at least one ticket to continue.");
35871
+ }
35749
35872
  const isBundle = method === "pass_bundle" || method === "membership_bundle";
35750
35873
  const bundleProductId = isBundle ? passId || membershipId : void 0;
35751
35874
  const selectionSnapshot = (() => {
@@ -35774,11 +35897,10 @@ function useEventRegistrationSubmit(options) {
35774
35897
  const result = await options.api.registerForEvent({
35775
35898
  occurrenceId: submitOccurrenceId,
35776
35899
  attendanceMode: options.selectedAttendanceMode ?? void 0,
35777
- ticketTypeId: options.selectedTicketTypeId ?? void 0,
35900
+ ticketSelections,
35778
35901
  selectedSlidingAmount: options.selectedSlidingAmount ?? void 0,
35779
35902
  email: options.formData.email.trim().toLowerCase(),
35780
35903
  name: options.formData.name.trim(),
35781
- ticketsCount: options.ticketsCount,
35782
35904
  phone: options.formData.phone.trim() || void 0,
35783
35905
  notes: options.formData.notes.trim() || void 0,
35784
35906
  altchaSolution: options.altchaSolution,
@@ -35823,10 +35945,9 @@ function useEventRegistrationSubmit(options) {
35823
35945
  savePendingRegistration(siteId, submitOccurrenceId, {
35824
35946
  attendeeId: result.attendeeId,
35825
35947
  formData: options.formData,
35826
- ticketsCount: options.ticketsCount,
35948
+ ticketSelections,
35827
35949
  selectedEventId: options.registrationTarget?.selectedEventId ?? null,
35828
35950
  selectedAttendanceMode: options.selectedAttendanceMode,
35829
- selectedTicketTypeId: options.selectedTicketTypeId ?? null,
35830
35951
  paymentSelection: selectionSnapshot
35831
35952
  });
35832
35953
  }
@@ -35880,6 +36001,7 @@ var init_useEventRegistrationSubmit = __esm({
35880
36001
  "use strict";
35881
36002
  "use client";
35882
36003
  init_sessionStorage();
36004
+ init_ticketSelectionLines();
35883
36005
  }
35884
36006
  });
35885
36007
 
@@ -35946,7 +36068,6 @@ function useEventRegistrationWizard(options) {
35946
36068
  createInitialRegistrationFlowState
35947
36069
  );
35948
36070
  const step = flowState.step;
35949
- const ticketsCount = flowState.ticketsCount;
35950
36071
  const paymentSelection = flowState.paymentSelection;
35951
36072
  const [selectedEvent, setSelectedEvent] = useState4(preSelectedEvent ?? null);
35952
36073
  const [formData, setFormData] = useState4({ name: "", email: "", phone: "", notes: "" });
@@ -35957,7 +36078,7 @@ function useEventRegistrationWizard(options) {
35957
36078
  const [selectedAttendanceMode, setSelectedAttendanceMode] = useState4(
35958
36079
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
35959
36080
  );
35960
- const [selectedTicketTypeId, setSelectedTicketTypeId] = useState4(null);
36081
+ const [selectedTicketLines, setSelectedTicketLines] = useState4([]);
35961
36082
  const [selectedSlidingAmountInput, setSelectedSlidingAmountInput] = useState4(() => {
35962
36083
  const slidingScale = getSlidingScaleForEvent(preSelectedEvent ?? null);
35963
36084
  return slidingScale?.suggestedAmount !== null && slidingScale?.suggestedAmount !== void 0 ? formatCentsAsCurrencyInput(slidingScale.suggestedAmount) : "";
@@ -35993,14 +36114,51 @@ function useEventRegistrationWizard(options) {
35993
36114
  [options.occurrenceContext, selectedEvent]
35994
36115
  );
35995
36116
  const occurrenceId = registrationTarget?.occurrenceId ?? "";
36117
+ const currentEvent = selectedEvent ?? preSelectedEvent;
36118
+ const currentEventPricing = currentEvent ? resolveRuntimeEventPricing(currentEvent) : null;
36119
+ const currentEventUsesTicketTypes = currentEventPricing !== null && publicEventPricingMode(currentEventPricing) === "ticket_types";
36120
+ const selectedModeAvailability = selectedAttendanceMode ? (currentEvent?.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
36121
+ const currentAvailableSpots = selectedModeAvailability?.availableSpots ?? currentEvent?.availableSpots;
36122
+ const currentWaitlistEnabled = selectedModeAvailability?.waitlistEnabled ?? currentEvent?.waitlistEnabled ?? false;
36123
+ const currentCanJoinWaitlist = currentAvailableSpots === 0 && currentWaitlistEnabled === true;
36124
+ const usesTicketTypeSelections = currentEventUsesTicketTypes && !currentCanJoinWaitlist;
36125
+ const eventTicketTypesForSelection = useMemo7(
36126
+ () => getVisibleTicketTypes({
36127
+ event: currentEvent,
36128
+ eligibility: null,
36129
+ selectedAttendanceMode
36130
+ }),
36131
+ [currentEvent, selectedAttendanceMode]
36132
+ );
36133
+ const normalizedSelectedTicketLines = useMemo7(
36134
+ () => normalizeTicketSelectionLines(selectedTicketLines),
36135
+ [selectedTicketLines]
36136
+ );
36137
+ const ticketTypeSelectionTotal = useMemo7(
36138
+ () => getTicketSelectionTotal(normalizedSelectedTicketLines),
36139
+ [normalizedSelectedTicketLines]
36140
+ );
36141
+ const ticketsCount = usesTicketTypeSelections ? ticketTypeSelectionTotal : flowState.ticketsCount;
36142
+ const ticketSelections = useMemo7(() => {
36143
+ if (waitlistClaim.claim) {
36144
+ return [{ ticketTypeId: null, quantity: 1 }];
36145
+ }
36146
+ if (usesTicketTypeSelections) {
36147
+ return normalizedSelectedTicketLines;
36148
+ }
36149
+ return [{ ticketTypeId: null, quantity: ticketsCount }];
36150
+ }, [normalizedSelectedTicketLines, ticketsCount, usesTicketTypeSelections, waitlistClaim.claim]);
36151
+ useEffect14(() => {
36152
+ if (flowState.ticketsCount === ticketsCount) return;
36153
+ dispatchFlow({ type: "set-tickets-count", ticketsCount });
36154
+ }, [flowState.ticketsCount, ticketsCount]);
35996
36155
  const eligibilityResult = useEligibility({
35997
36156
  enabled: portalAuthState === "authenticated" || step === "enter-details" || step === "select-payment",
35998
36157
  occurrenceId,
35999
36158
  attendanceMode: selectedAttendanceMode,
36000
- ticketTypeId: selectedTicketTypeId,
36159
+ ticketSelections,
36001
36160
  selectedSlidingAmount: parseCurrencyInputToCents(selectedSlidingAmountInput),
36002
- email: formData.email,
36003
- ticketsCount
36161
+ email: formData.email
36004
36162
  });
36005
36163
  const existingRegistrationResult = useExistingEventRegistration({
36006
36164
  isAuthenticated: portalAuthState === "authenticated",
@@ -36106,7 +36264,7 @@ function useEventRegistrationWizard(options) {
36106
36264
  setTicketsCount,
36107
36265
  setSelectedEvent,
36108
36266
  setSelectedAttendanceMode,
36109
- setSelectedTicketTypeId,
36267
+ setTicketSelections: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
36110
36268
  setPaymentSelection: (selection) => dispatchFlow({ type: "set-manual-payment-selection", selection }),
36111
36269
  setPendingAttendeeId,
36112
36270
  setStep,
@@ -36119,20 +36277,21 @@ function useEventRegistrationWizard(options) {
36119
36277
  }, [selectedEvent, preSelectedEvent]);
36120
36278
  const visibleTicketTypes = useMemo7(
36121
36279
  () => getVisibleTicketTypes({
36122
- event: selectedEvent ?? preSelectedEvent,
36280
+ event: currentEvent,
36123
36281
  eligibility,
36124
36282
  selectedAttendanceMode
36125
36283
  }),
36126
- [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility]
36284
+ [currentEvent, selectedAttendanceMode, eligibility]
36127
36285
  );
36286
+ const singleSelectedTicketTypeId = getSingleSelectedTicketTypeId(normalizedSelectedTicketLines);
36128
36287
  const selectedTicketType = useMemo7(
36129
36288
  () => findSelectedTicketType({
36130
- event: selectedEvent ?? preSelectedEvent,
36289
+ event: currentEvent,
36131
36290
  eligibility,
36132
- selectedTicketTypeId,
36291
+ selectedTicketTypeId: singleSelectedTicketTypeId,
36133
36292
  selectedAttendanceMode
36134
36293
  }),
36135
- [selectedAttendanceMode, selectedEvent, preSelectedEvent, eligibility, selectedTicketTypeId]
36294
+ [currentEvent, eligibility, selectedAttendanceMode, singleSelectedTicketTypeId]
36136
36295
  );
36137
36296
  const selectedSlidingAmount = useMemo7(
36138
36297
  () => parseCurrencyInputToCents(selectedSlidingAmountInput),
@@ -36147,37 +36306,44 @@ function useEventRegistrationWizard(options) {
36147
36306
  selectedAmount: selectedSlidingAmount
36148
36307
  });
36149
36308
  const ticketTypeSelectionRequired = useMemo7(() => {
36150
- const event = selectedEvent ?? preSelectedEvent;
36309
+ const event = currentEvent;
36151
36310
  if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
36152
36311
  return false;
36153
36312
  }
36154
36313
  if ((event.deliveryMode ?? "in_person") === "hybrid" && !selectedAttendanceMode) {
36155
36314
  return false;
36156
36315
  }
36157
- const selectedModeAvailability = selectedAttendanceMode ? (event.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
36158
- const canJoinWaitlist = (selectedModeAvailability?.availableSpots ?? event.availableSpots) === 0 && (selectedModeAvailability?.waitlistEnabled ?? event.waitlistEnabled) === true;
36316
+ const selectedModeAvailability2 = selectedAttendanceMode ? (event.attendanceModes ?? []).find((mode) => mode.mode === selectedAttendanceMode) ?? null : null;
36317
+ const canJoinWaitlist = (selectedModeAvailability2?.availableSpots ?? event.availableSpots) === 0 && (selectedModeAvailability2?.waitlistEnabled ?? event.waitlistEnabled) === true;
36159
36318
  return !canJoinWaitlist;
36160
- }, [selectedAttendanceMode, selectedEvent, preSelectedEvent]);
36161
- const hasValidSelectedTicketType = !ticketTypeSelectionRequired || Boolean(
36162
- selectedTicketType && selectedTicketType.availability.kind === "available"
36163
- );
36319
+ }, [currentEvent, selectedAttendanceMode]);
36320
+ const hasValidTicketSelection = !ticketTypeSelectionRequired || normalizedSelectedTicketLines.length > 0 && normalizedSelectedTicketLines.every((line) => {
36321
+ if (!line.ticketTypeId) return false;
36322
+ const ticketType = visibleTicketTypes.find((candidate) => candidate.id === line.ticketTypeId) ?? null;
36323
+ return ticketType?.availability.kind === "available";
36324
+ });
36164
36325
  useEffect14(() => {
36165
- const event = selectedEvent ?? preSelectedEvent;
36166
- if (!event || publicEventPricingMode(resolveRuntimeEventPricing(event)) !== "ticket_types") {
36167
- if (selectedTicketTypeId !== null) {
36168
- setSelectedTicketTypeId(null);
36326
+ if (!usesTicketTypeSelections) {
36327
+ if (selectedTicketLines.length > 0) {
36328
+ setSelectedTicketLines([]);
36169
36329
  }
36170
36330
  return;
36171
36331
  }
36172
- const implicitTicketType = getImplicitSingleAvailableTicketType(visibleTicketTypes);
36173
- if (implicitTicketType && selectedTicketTypeId !== implicitTicketType.id) {
36174
- setSelectedTicketTypeId(implicitTicketType.id);
36332
+ const availableTicketTypes = eventTicketTypesForSelection.filter(
36333
+ (ticketType) => ticketType.availability.kind === "available"
36334
+ );
36335
+ const implicitTicketType = getImplicitSingleAvailableTicketType(eventTicketTypesForSelection);
36336
+ if (implicitTicketType && normalizedSelectedTicketLines.length === 0) {
36337
+ setSelectedTicketLines([{ ticketTypeId: implicitTicketType.id, quantity: 1 }]);
36175
36338
  return;
36176
36339
  }
36177
- if (selectedTicketTypeId && !visibleTicketTypes.some((ticketType) => ticketType.id === selectedTicketTypeId)) {
36178
- setSelectedTicketTypeId(null);
36340
+ const filteredLines = normalizedSelectedTicketLines.filter(
36341
+ (line) => line.ticketTypeId && availableTicketTypes.some((ticketType) => ticketType.id === line.ticketTypeId)
36342
+ );
36343
+ if (filteredLines.length !== normalizedSelectedTicketLines.length) {
36344
+ setSelectedTicketLines(filteredLines);
36179
36345
  }
36180
- }, [selectedEvent, preSelectedEvent, selectedTicketTypeId, visibleTicketTypes]);
36346
+ }, [eventTicketTypesForSelection, normalizedSelectedTicketLines, selectedTicketLines.length, usesTicketTypeSelections]);
36181
36347
  useEffect14(() => {
36182
36348
  if (!requireGuestDetails) {
36183
36349
  if (guests.length > 0) setGuests([]);
@@ -36221,21 +36387,20 @@ function useEventRegistrationWizard(options) {
36221
36387
  return resolveEventRequiresPayment({
36222
36388
  eligibility,
36223
36389
  selectedTicketType,
36224
- event: selectedEvent ?? preSelectedEvent
36390
+ event: currentEvent
36225
36391
  });
36226
36392
  },
36227
36393
  [
36228
36394
  eligibility,
36229
36395
  selectedTicketType,
36230
- selectedEvent,
36231
- preSelectedEvent,
36396
+ currentEvent,
36232
36397
  selectedEventSlidingScale,
36233
36398
  selectedSlidingAmount
36234
36399
  ]
36235
36400
  );
36236
36401
  const handleDetailsComplete = async () => {
36237
- if (ticketTypeSelectionRequired && !hasValidSelectedTicketType) {
36238
- const message = "Select a ticket type to continue.";
36402
+ if (ticketTypeSelectionRequired && !hasValidTicketSelection) {
36403
+ const message = "Choose at least one available ticket to continue.";
36239
36404
  setErrorMessage(message);
36240
36405
  setStep("error");
36241
36406
  return { ok: false, reason: "blocked", message };
@@ -36320,27 +36485,39 @@ function useEventRegistrationWizard(options) {
36320
36485
  const discountContext = useMemo7(
36321
36486
  () => resolveDiscountContext({
36322
36487
  paymentSelection,
36323
- event: selectedEvent ?? preSelectedEvent,
36488
+ event: currentEvent,
36324
36489
  eventRequiresPayment,
36325
36490
  ticketsCount
36326
36491
  }),
36327
- [eventRequiresPayment, paymentSelection, selectedEvent, preSelectedEvent, ticketsCount]
36492
+ [currentEvent, eventRequiresPayment, paymentSelection, ticketsCount]
36493
+ );
36494
+ const fallbackUnitPriceCents = useMemo7(
36495
+ () => currentEvent ? publicEventScalarPrice(resolveRuntimeEventPricing(currentEvent)) ?? 0 : 0,
36496
+ [currentEvent]
36497
+ );
36498
+ const ticketSelectionSubtotalCents = useMemo7(() => {
36499
+ if (!usesTicketTypeSelections) {
36500
+ return Math.max(0, eligibility?.price ?? fallbackUnitPriceCents * ticketsCount);
36501
+ }
36502
+ return calculateTicketSelectionSubtotal({
36503
+ lines: normalizedSelectedTicketLines,
36504
+ ticketTypes: visibleTicketTypes,
36505
+ fallbackUnitPriceCents
36506
+ });
36507
+ }, [eligibility, fallbackUnitPriceCents, normalizedSelectedTicketLines, ticketsCount, usesTicketTypeSelections, visibleTicketTypes]);
36508
+ const ticketSelectionSummaryLines = useMemo7(
36509
+ () => buildTicketSelectionSummaryLines({
36510
+ lines: ticketSelections,
36511
+ ticketTypes: visibleTicketTypes,
36512
+ fallbackLabel: "Ticket",
36513
+ fallbackUnitPriceCents
36514
+ }),
36515
+ [fallbackUnitPriceCents, ticketSelections, visibleTicketTypes]
36328
36516
  );
36329
36517
  const discountSubtotalCents = useMemo7(() => {
36330
36518
  if (discountContext?.type !== "event") return void 0;
36331
- const event = selectedEvent ?? preSelectedEvent;
36332
- const eventPriceCents = selectedTicketType?.priceAmount ?? eligibility?.price ?? (selectedEventSlidingScale ? selectedSlidingAmount : null) ?? (event ? publicEventScalarPrice(resolveRuntimeEventPricing(event)) : null) ?? 0;
36333
- return Math.max(0, eventPriceCents * ticketsCount);
36334
- }, [
36335
- discountContext,
36336
- selectedTicketType,
36337
- eligibility,
36338
- selectedEvent,
36339
- preSelectedEvent,
36340
- selectedEventSlidingScale,
36341
- selectedSlidingAmount,
36342
- ticketsCount
36343
- ]);
36519
+ return ticketSelectionSubtotalCents;
36520
+ }, [discountContext, ticketSelectionSubtotalCents]);
36344
36521
  const discountUi = useDiscountCodeUi({ api, discountContext, subtotalCents: discountSubtotalCents });
36345
36522
  const selectionMethod = paymentSelection.method;
36346
36523
  const shouldShowDiscountToggle = discountContext !== null && discountUi.manualCodeEntryAllowed && (selectionMethod === "stripe" || selectionMethod === "pass_bundle" || selectionMethod === "membership_bundle");
@@ -36348,9 +36525,8 @@ function useEventRegistrationWizard(options) {
36348
36525
  api,
36349
36526
  registrationTarget,
36350
36527
  selectedAttendanceMode,
36351
- selectedTicketTypeId,
36528
+ ticketSelections,
36352
36529
  selectedSlidingAmount,
36353
- ticketsCount,
36354
36530
  formData,
36355
36531
  altchaSolution,
36356
36532
  requireGuestDetails,
@@ -36371,12 +36547,12 @@ function useEventRegistrationWizard(options) {
36371
36547
  setSelectedAttendanceMode(
36372
36548
  (event.deliveryMode ?? "in_person") === "hybrid" ? null : (event.deliveryMode ?? "in_person") === "online" ? "online" : "in_person"
36373
36549
  );
36374
- setSelectedTicketTypeId(null);
36550
+ setSelectedTicketLines([]);
36375
36551
  dispatchFlow({ type: "event-changed", hasPreSelectedContext: true });
36376
36552
  };
36377
36553
  const handleRetry = () => {
36378
36554
  dispatchFlow({ type: "reset", hasPreSelectedContext });
36379
- setSelectedTicketTypeId(null);
36555
+ setSelectedTicketLines([]);
36380
36556
  setSelectedSlidingAmountInput("");
36381
36557
  setSelectedAttendanceMode(
36382
36558
  preSelectedEvent?.deliveryMode === "online" ? "online" : preSelectedEvent?.deliveryMode === "in_person" ? "in_person" : null
@@ -36412,17 +36588,20 @@ function useEventRegistrationWizard(options) {
36412
36588
  setTicketsCount,
36413
36589
  selectedAttendanceMode,
36414
36590
  setSelectedAttendanceMode,
36415
- selectedTicketTypeId,
36416
- setSelectedTicketTypeId,
36591
+ ticketSelections,
36592
+ selectedTicketLines: normalizedSelectedTicketLines,
36593
+ setSelectedTicketLines: (lines) => setSelectedTicketLines(normalizeTicketSelectionLines(lines)),
36417
36594
  selectedSlidingAmountInput,
36418
36595
  setSelectedSlidingAmountInput,
36419
36596
  selectedSlidingAmount,
36420
36597
  selectedEventSlidingScale,
36421
36598
  slidingScaleSelectionMessage,
36422
36599
  selectedTicketType,
36600
+ ticketSelectionSummaryLines,
36601
+ ticketSelectionSubtotalCents,
36423
36602
  visibleTicketTypes,
36424
36603
  ticketTypeSelectionRequired,
36425
- hasValidSelectedTicketType,
36604
+ hasValidTicketSelection,
36426
36605
  eventRequiresPayment,
36427
36606
  formData,
36428
36607
  setFormData,
@@ -36492,6 +36671,7 @@ var init_useEventRegistrationWizard = __esm({
36492
36671
  init_useEventRegistrationSubmit();
36493
36672
  init_useDiscountCodeUi();
36494
36673
  init_utils3();
36674
+ init_ticketSelectionLines();
36495
36675
  init_registrationStateMachine();
36496
36676
  init_event_registration_headless_state();
36497
36677
  init_registrationDerivations();