@bigz-app/booking-widget 1.4.4 → 1.5.1

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 (47) hide show
  1. package/dist/booking-widget.js +17856 -17103
  2. package/dist/booking-widget.js.map +1 -1
  3. package/dist/components/UniversalBookingWidget.d.ts +30 -4
  4. package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
  5. package/dist/components/booking/BookingForm.d.ts +1 -3
  6. package/dist/components/booking/BookingForm.d.ts.map +1 -1
  7. package/dist/components/booking/GiftCardOnlyBooking.d.ts.map +1 -1
  8. package/dist/components/booking/HoldCountdown.d.ts +10 -0
  9. package/dist/components/booking/HoldCountdown.d.ts.map +1 -0
  10. package/dist/components/booking/MolliePaymentForm.d.ts +11 -1
  11. package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
  12. package/dist/components/booking/StripePaymentForm.d.ts +11 -2
  13. package/dist/components/booking/StripePaymentForm.d.ts.map +1 -1
  14. package/dist/components/events/EventInstanceSelection.d.ts +3 -1
  15. package/dist/components/events/EventInstanceSelection.d.ts.map +1 -1
  16. package/dist/components/events/EventTypeDetailsDialog.d.ts +0 -1
  17. package/dist/components/events/EventTypeDetailsDialog.d.ts.map +1 -1
  18. package/dist/components/events/EventTypeSelection.d.ts +16 -1
  19. package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
  20. package/dist/components/events/FreeformSelection.d.ts +49 -0
  21. package/dist/components/events/FreeformSelection.d.ts.map +1 -0
  22. package/dist/components/events/WaitlistDialog.d.ts +10 -0
  23. package/dist/components/events/WaitlistDialog.d.ts.map +1 -0
  24. package/dist/components/events/index.d.ts +1 -0
  25. package/dist/components/events/index.d.ts.map +1 -1
  26. package/dist/components/voucher/VoucherIntegration.d.ts +3 -2
  27. package/dist/components/voucher/VoucherIntegration.d.ts.map +1 -1
  28. package/dist/components/voucher/VoucherPurchaseForm.d.ts +8 -3
  29. package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
  30. package/dist/components/voucher/index.d.ts +1 -1
  31. package/dist/components/voucher/index.d.ts.map +1 -1
  32. package/dist/components/voucher/useVoucherConfig.d.ts +6 -1
  33. package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
  34. package/dist/i18n/locales/de.d.ts.map +1 -1
  35. package/dist/i18n/locales/en.d.ts.map +1 -1
  36. package/dist/i18n/locales/es.d.ts.map +1 -1
  37. package/dist/i18n/locales/pt.d.ts.map +1 -1
  38. package/dist/i18n/locales/sv.d.ts.map +1 -1
  39. package/dist/index.cjs +1235 -482
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.esm.js +1228 -475
  42. package/dist/index.esm.js.map +1 -1
  43. package/dist/utils/analytics.d.ts +3 -1
  44. package/dist/utils/analytics.d.ts.map +1 -1
  45. package/dist/utils.d.ts.map +1 -1
  46. package/dist/validation/booking-schema.d.ts +4 -4
  47. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -6,188 +6,24 @@ var jsxRuntime = require('react/jsx-runtime');
6
6
  var ReactDOM = require('react-dom');
7
7
 
8
8
  function _interopNamespaceDefault(e) {
9
- var n = Object.create(null);
10
- if (e) {
11
- Object.keys(e).forEach(function (k) {
12
- if (k !== 'default') {
13
- var d = Object.getOwnPropertyDescriptor(e, k);
14
- Object.defineProperty(n, k, d.get ? d : {
15
- enumerable: true,
16
- get: function () { return e[k]; }
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
17
19
  });
18
- }
19
- });
20
- }
21
- n.default = e;
22
- return Object.freeze(n);
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
23
  }
24
24
 
25
25
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
26
26
 
27
- var V3_URL = 'https://js.stripe.com/v3';
28
- var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
29
- var EXISTING_SCRIPT_MESSAGE = 'loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used';
30
- var findScript = function findScript() {
31
- var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
32
-
33
- for (var i = 0; i < scripts.length; i++) {
34
- var script = scripts[i];
35
-
36
- if (!V3_URL_REGEX.test(script.src)) {
37
- continue;
38
- }
39
-
40
- return script;
41
- }
42
-
43
- return null;
44
- };
45
-
46
- var injectScript = function injectScript(params) {
47
- var queryString = '';
48
- var script = document.createElement('script');
49
- script.src = "".concat(V3_URL).concat(queryString);
50
- var headOrBody = document.head || document.body;
51
-
52
- if (!headOrBody) {
53
- throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
54
- }
55
-
56
- headOrBody.appendChild(script);
57
- return script;
58
- };
59
-
60
- var registerWrapper = function registerWrapper(stripe, startTime) {
61
- if (!stripe || !stripe._registerWrapper) {
62
- return;
63
- }
64
-
65
- stripe._registerWrapper({
66
- name: 'stripe-js',
67
- version: "4.6.0",
68
- startTime: startTime
69
- });
70
- };
71
-
72
- var stripePromise = null;
73
- var onErrorListener = null;
74
- var onLoadListener = null;
75
-
76
- var onError = function onError(reject) {
77
- return function () {
78
- reject(new Error('Failed to load Stripe.js'));
79
- };
80
- };
81
-
82
- var onLoad = function onLoad(resolve, reject) {
83
- return function () {
84
- if (window.Stripe) {
85
- resolve(window.Stripe);
86
- } else {
87
- reject(new Error('Stripe.js not available'));
88
- }
89
- };
90
- };
91
-
92
- var loadScript = function loadScript(params) {
93
- // Ensure that we only attempt to load Stripe.js at most once
94
- if (stripePromise !== null) {
95
- return stripePromise;
96
- }
97
-
98
- stripePromise = new Promise(function (resolve, reject) {
99
- if (typeof window === 'undefined' || typeof document === 'undefined') {
100
- // Resolve to null when imported server side. This makes the module
101
- // safe to import in an isomorphic code base.
102
- resolve(null);
103
- return;
104
- }
105
-
106
- if (window.Stripe) {
107
- resolve(window.Stripe);
108
- return;
109
- }
110
-
111
- try {
112
- var script = findScript();
113
-
114
- if (script && params) ; else if (!script) {
115
- script = injectScript(params);
116
- } else if (script && onLoadListener !== null && onErrorListener !== null) {
117
- var _script$parentNode;
118
-
119
- // remove event listeners
120
- script.removeEventListener('load', onLoadListener);
121
- script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
122
- // reload script to trigger 'load' event
123
-
124
- (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
125
- script = injectScript(params);
126
- }
127
-
128
- onLoadListener = onLoad(resolve, reject);
129
- onErrorListener = onError(reject);
130
- script.addEventListener('load', onLoadListener);
131
- script.addEventListener('error', onErrorListener);
132
- } catch (error) {
133
- reject(error);
134
- return;
135
- }
136
- }); // Resets stripePromise on error
137
-
138
- return stripePromise["catch"](function (error) {
139
- stripePromise = null;
140
- return Promise.reject(error);
141
- });
142
- };
143
- var initStripe = function initStripe(maybeStripe, args, startTime) {
144
- if (maybeStripe === null) {
145
- return null;
146
- }
147
-
148
- var stripe = maybeStripe.apply(undefined, args);
149
- registerWrapper(stripe, startTime);
150
- return stripe;
151
- }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
152
-
153
- var stripePromise$1;
154
- var loadCalled = false;
155
-
156
- var getStripePromise = function getStripePromise() {
157
- if (stripePromise$1) {
158
- return stripePromise$1;
159
- }
160
-
161
- stripePromise$1 = loadScript(null)["catch"](function (error) {
162
- // clear cache on error
163
- stripePromise$1 = null;
164
- return Promise.reject(error);
165
- });
166
- return stripePromise$1;
167
- }; // Execute our own script injection after a tick to give users time to do their
168
- // own script injection.
169
-
170
-
171
- Promise.resolve().then(function () {
172
- return getStripePromise();
173
- })["catch"](function (error) {
174
- if (!loadCalled) {
175
- console.warn(error);
176
- }
177
- });
178
- var loadStripe = function loadStripe() {
179
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
180
- args[_key] = arguments[_key];
181
- }
182
-
183
- loadCalled = true;
184
- var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
185
-
186
- return getStripePromise().then(function (maybeStripe) {
187
- return initStripe(maybeStripe, args, startTime);
188
- });
189
- };
190
-
191
27
  const de$1 = {
192
28
  // Common
193
29
  "common.back": "← Zurück",
@@ -267,6 +103,24 @@ const de$1 = {
267
103
  "instances.bestPrice": "bester Preis !!!",
268
104
  "instances.goodPrice": "günstiger Preis",
269
105
  "instances.oclock": "Uhr",
106
+ // Warteliste
107
+ "waitlist.joinCta": "Auf die Warteliste",
108
+ "waitlist.title": "Auf die Warteliste setzen",
109
+ "waitlist.subtitle": "{{name}} ist ausgebucht. Hinterlassen Sie Ihre Daten und wir melden uns, falls ein Platz frei wird.",
110
+ "waitlist.name": "Name",
111
+ "waitlist.email": "E-Mail",
112
+ "waitlist.phone": "Telefon (optional)",
113
+ "waitlist.requiredFields": "Bitte geben Sie Ihren Namen und Ihre E-Mail-Adresse ein.",
114
+ "waitlist.join": "Eintragen",
115
+ "waitlist.submitting": "Wird eingetragen...",
116
+ "waitlist.cancel": "Abbrechen",
117
+ "waitlist.submitError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.",
118
+ "waitlist.successTitle": "Sie sind auf der Liste!",
119
+ "waitlist.successMessage": "Sie sind Nummer {{position}} auf der Warteliste. Wir melden uns, sobald ein Platz frei wird.",
120
+ "waitlist.done": "Fertig",
121
+ // Reservierungs-Countdown
122
+ "hold.reservedFor": "Platz reserviert für {{time}}",
123
+ "hold.expired": "Ihre Reservierung ist abgelaufen",
270
124
  // Next events preview
271
125
  "nextEvents.title": "Nächste verfügbare Termine",
272
126
  "nextEvents.subtitle": "Wähle einen Termin aus oder zeige alle verfügbaren Termine an",
@@ -315,6 +169,10 @@ const de$1 = {
315
169
  "booking.stepUpsells": "Extras",
316
170
  "booking.stepDetails": "Angaben",
317
171
  "booking.stepPayment": "Zahlung",
172
+ "booking.steps": "{{count}} Schritt(e)",
173
+ "booking.freeformUnitsAvailable": "{{count}} Einheiten verfuegbar",
174
+ "booking.freeformNotAvailable": "Das gewaehlte Zeitfenster ist nicht verfuegbar",
175
+ "booking.freeformMissingConfig": "Die Freeform-Konfiguration fuer diesen Event-Typ fehlt.",
318
176
  // Booking summary
319
177
  "summary.title": "Buchungszusammenfassung",
320
178
  "summary.subtotal": "Zwischensumme:",
@@ -370,6 +228,13 @@ const de$1 = {
370
228
  "voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
371
229
  "voucher.eventType": "Erlebnisgutschein",
372
230
  "voucher.eventTypeDesc": "Für ein bestimmtes Erlebnis",
231
+ "voucher.categoryType": "Kategoriegutschein",
232
+ "voucher.categoryTypeDesc": "Für ein beliebiges Erlebnis einer Kategorie",
233
+ "voucher.categoryVoucher": "Kategoriegutschein",
234
+ "voucher.selectCategory": "Kategorie wählen",
235
+ "voucher.categoryLabel": "Kategorie",
236
+ "voucher.selectCategoryPlaceholder": "Kategorie auswählen...",
237
+ "voucher.categoryAmount": "Gutscheinwert",
373
238
  "voucher.selectAmount": "Betrag wählen",
374
239
  "voucher.customAmount": "Oder eigenen Betrag eingeben",
375
240
  "voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
@@ -557,6 +422,24 @@ const en = {
557
422
  "instances.bestPrice": "best price !!!",
558
423
  "instances.goodPrice": "good price",
559
424
  "instances.oclock": "",
425
+ // Waitlist
426
+ "waitlist.joinCta": "Join waitlist",
427
+ "waitlist.title": "Join the waitlist",
428
+ "waitlist.subtitle": "{{name}} is fully booked. Leave your details and we'll contact you if a spot opens up.",
429
+ "waitlist.name": "Name",
430
+ "waitlist.email": "Email",
431
+ "waitlist.phone": "Phone (optional)",
432
+ "waitlist.requiredFields": "Please enter your name and email.",
433
+ "waitlist.join": "Join waitlist",
434
+ "waitlist.submitting": "Joining...",
435
+ "waitlist.cancel": "Cancel",
436
+ "waitlist.submitError": "Something went wrong. Please try again.",
437
+ "waitlist.successTitle": "You're on the list!",
438
+ "waitlist.successMessage": "You are number {{position}} on the waitlist. We'll be in touch if a spot becomes available.",
439
+ "waitlist.done": "Done",
440
+ // Slot hold countdown
441
+ "hold.reservedFor": "Slot reserved for {{time}}",
442
+ "hold.expired": "Your reservation has expired",
560
443
  // Next events preview
561
444
  "nextEvents.title": "Next available dates",
562
445
  "nextEvents.subtitle": "Select a date or view all available dates",
@@ -605,6 +488,10 @@ const en = {
605
488
  "booking.stepUpsells": "Extras",
606
489
  "booking.stepDetails": "Details",
607
490
  "booking.stepPayment": "Payment",
491
+ "booking.steps": "{{count}} step(s)",
492
+ "booking.freeformUnitsAvailable": "{{count}} units available",
493
+ "booking.freeformNotAvailable": "Selected window is not available",
494
+ "booking.freeformMissingConfig": "Missing freeform configuration for this event type.",
608
495
  // Booking summary
609
496
  "summary.title": "Booking Summary",
610
497
  "summary.subtotal": "Subtotal:",
@@ -660,6 +547,13 @@ const en = {
660
547
  "voucher.monetaryTypeDesc": "Choose any amount",
661
548
  "voucher.eventType": "Event Voucher",
662
549
  "voucher.eventTypeDesc": "For a specific experience",
550
+ "voucher.categoryType": "Category Voucher",
551
+ "voucher.categoryTypeDesc": "For any experience in a category",
552
+ "voucher.categoryVoucher": "Category Voucher",
553
+ "voucher.selectCategory": "Select Category",
554
+ "voucher.categoryLabel": "Category",
555
+ "voucher.selectCategoryPlaceholder": "Choose a category...",
556
+ "voucher.categoryAmount": "Voucher Value",
663
557
  "voucher.selectAmount": "Select Amount",
664
558
  "voucher.customAmount": "Or enter custom amount",
665
559
  "voucher.amountRange": "Amount must be between {{min}} and {{max}}",
@@ -950,6 +844,13 @@ const es = {
950
844
  "voucher.monetaryTypeDesc": "Elige cualquier cantidad",
951
845
  "voucher.eventType": "Vale de experiencia",
952
846
  "voucher.eventTypeDesc": "Para una experiencia específica",
847
+ "voucher.categoryType": "Vale de categoría",
848
+ "voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
849
+ "voucher.categoryVoucher": "Vale de categoría",
850
+ "voucher.selectCategory": "Seleccionar categoría",
851
+ "voucher.categoryLabel": "Categoría",
852
+ "voucher.selectCategoryPlaceholder": "Elige una categoría...",
853
+ "voucher.categoryAmount": "Valor del vale",
953
854
  "voucher.selectAmount": "Selecciona el importe",
954
855
  "voucher.customAmount": "O introduce un importe personalizado",
955
856
  "voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
@@ -1240,6 +1141,13 @@ const pt = {
1240
1141
  "voucher.monetaryTypeDesc": "Escolha qualquer quantia",
1241
1142
  "voucher.eventType": "Vale de experiência",
1242
1143
  "voucher.eventTypeDesc": "Para uma experiência específica",
1144
+ "voucher.categoryType": "Vale de categoria",
1145
+ "voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
1146
+ "voucher.categoryVoucher": "Vale de categoria",
1147
+ "voucher.selectCategory": "Selecionar categoria",
1148
+ "voucher.categoryLabel": "Categoria",
1149
+ "voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
1150
+ "voucher.categoryAmount": "Valor do vale",
1243
1151
  "voucher.selectAmount": "Selecione o valor",
1244
1152
  "voucher.customAmount": "Ou introduza um valor personalizado",
1245
1153
  "voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
@@ -1530,6 +1438,13 @@ const sv = {
1530
1438
  "voucher.monetaryTypeDesc": "Välj valfritt belopp",
1531
1439
  "voucher.eventType": "Upplevelsebevis",
1532
1440
  "voucher.eventTypeDesc": "För en specifik upplevelse",
1441
+ "voucher.categoryType": "Kategoripresent",
1442
+ "voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
1443
+ "voucher.categoryVoucher": "Kategoripresent",
1444
+ "voucher.selectCategory": "Välj kategori",
1445
+ "voucher.categoryLabel": "Kategori",
1446
+ "voucher.selectCategoryPlaceholder": "Välj en kategori...",
1447
+ "voucher.categoryAmount": "Presentkortsvärde",
1533
1448
  "voucher.selectAmount": "Välj belopp",
1534
1449
  "voucher.customAmount": "Eller ange eget belopp",
1535
1450
  "voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
@@ -2262,6 +2177,7 @@ const inferConfigFromUrl = (baseConfig) => {
2262
2177
  const categoryId = urlParams.get("categoryId") || urlParams.get("category");
2263
2178
  const eventTypeId = urlParams.get("eventTypeId") || urlParams.get("type");
2264
2179
  const eventTypeIds = urlParams.get("eventTypeIds") || urlParams.get("types");
2180
+ const partnerContractId = urlParams.get("partnerContractId") || urlParams.get("contractId");
2265
2181
  const voucherValue = urlParams.get("voucherValue");
2266
2182
  const voucherCategory = urlParams.get("voucherCategory");
2267
2183
  const voucherEventType = urlParams.get("voucherEventType");
@@ -2274,11 +2190,14 @@ const inferConfigFromUrl = (baseConfig) => {
2274
2190
  : ["0", "false", "no", "off"].includes(voucherCardIntegration.toLowerCase())
2275
2191
  ? false
2276
2192
  : undefined;
2277
- const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, ...rest }) => rest;
2193
+ const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, partnerContractId: _pc, ...rest }) => rest;
2278
2194
  if (eventInstanceId && !Number.isNaN(Number(eventInstanceId))) {
2279
2195
  return {
2280
2196
  ...omitSelectionKeys(baseConfig),
2281
2197
  eventInstanceId: Number(eventInstanceId),
2198
+ ...(partnerContractId
2199
+ ? { partnerContractId }
2200
+ : {}),
2282
2201
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2283
2202
  };
2284
2203
  }
@@ -2286,6 +2205,9 @@ const inferConfigFromUrl = (baseConfig) => {
2286
2205
  return {
2287
2206
  ...omitSelectionKeys(baseConfig),
2288
2207
  categoryId: Number(categoryId),
2208
+ ...(partnerContractId
2209
+ ? { partnerContractId }
2210
+ : {}),
2289
2211
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2290
2212
  };
2291
2213
  }
@@ -2293,6 +2215,9 @@ const inferConfigFromUrl = (baseConfig) => {
2293
2215
  return {
2294
2216
  ...omitSelectionKeys(baseConfig),
2295
2217
  eventTypeId: Number(eventTypeId),
2218
+ ...(partnerContractId
2219
+ ? { partnerContractId }
2220
+ : {}),
2296
2221
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2297
2222
  };
2298
2223
  }
@@ -2305,6 +2230,9 @@ const inferConfigFromUrl = (baseConfig) => {
2305
2230
  return {
2306
2231
  ...omitSelectionKeys(baseConfig),
2307
2232
  eventTypeIds: typeIds,
2233
+ ...(partnerContractId
2234
+ ? { partnerContractId }
2235
+ : {}),
2308
2236
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2309
2237
  };
2310
2238
  }
@@ -2321,6 +2249,9 @@ const inferConfigFromUrl = (baseConfig) => {
2321
2249
  if (parsedVoucherValues.length || parsedVoucherCategories.length || parsedVoucherEventTypes.length) {
2322
2250
  return {
2323
2251
  ...baseConfig,
2252
+ ...(partnerContractId
2253
+ ? { partnerContractId }
2254
+ : {}),
2324
2255
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2325
2256
  ...(parsedVoucherValues.length > 0
2326
2257
  ? { voucherValue: parsedVoucherValues.length === 1 ? parsedVoucherValues[0] : parsedVoucherValues }
@@ -2338,6 +2269,9 @@ const inferConfigFromUrl = (baseConfig) => {
2338
2269
  }
2339
2270
  return {
2340
2271
  ...baseConfig,
2272
+ ...(partnerContractId
2273
+ ? { partnerContractId }
2274
+ : {}),
2341
2275
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2342
2276
  };
2343
2277
  };
@@ -6350,6 +6284,7 @@ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, gif
6350
6284
  customerPhone: formData.customerPhone?.trim(),
6351
6285
  comment: formData.comment?.trim(),
6352
6286
  paymentMethod: "gift_card",
6287
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6353
6288
  ...(upsellSelections.length > 0 && { upsellSelections }),
6354
6289
  };
6355
6290
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
@@ -6480,7 +6415,7 @@ function loadMollieScript() {
6480
6415
  document.head.appendChild(script);
6481
6416
  });
6482
6417
  }
6483
- function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
6418
+ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
6484
6419
  const t = useTranslations();
6485
6420
  const { locale } = useLocale();
6486
6421
  const [isLoading, setIsLoading] = React.useState(false);
@@ -6494,7 +6429,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6494
6429
  const cardFormRef = React.useRef(null);
6495
6430
  const cardContainerRef = React.useRef(null);
6496
6431
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6497
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6432
+ const isFullyCoveredByGiftCards = freeformSelection
6433
+ ? false
6434
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6498
6435
  const isCreditCard = selectedMethod === "creditcard";
6499
6436
  React.useEffect(() => {
6500
6437
  if (isFullyCoveredByGiftCards)
@@ -6604,6 +6541,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6604
6541
  return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
6605
6542
  }
6606
6543
  const validateBeforePayment = () => {
6544
+ if (freeformSelection) {
6545
+ return null;
6546
+ }
6607
6547
  const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
6608
6548
  const availableSpots = eventDetails.availableSpots || 0;
6609
6549
  if (participantCount > availableSpots) {
@@ -6615,20 +6555,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6615
6555
  return null;
6616
6556
  };
6617
6557
  const buildRequestData = (cardToken) => ({
6618
- eventInstanceId: config.eventInstanceId || eventDetails.id,
6619
- organizationId: config.organizationId,
6620
- amount: Math.round(totalAmount),
6621
- currency: "EUR",
6622
- participants: formData.participants.filter((p) => p.name?.trim()),
6623
- discountCode: discountCode?.code,
6624
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6625
- customerName: formData.customerName?.trim(),
6626
- customerEmail: formData.customerEmail?.trim(),
6627
- customerPhone: formData.customerPhone?.trim(),
6628
- comment: formData.comment?.trim(),
6629
- ...(cardToken && { cardToken }),
6630
- ...(selectedMethod && !cardToken && { method: selectedMethod }),
6631
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6558
+ ...(freeformSelection
6559
+ ? {
6560
+ eventTypeId: freeformSelection.eventTypeId,
6561
+ organizationId: config.organizationId,
6562
+ start: freeformSelection.start,
6563
+ end: freeformSelection.end,
6564
+ requestedUnits: freeformSelection.requestedUnits,
6565
+ amount: Math.round(totalAmount),
6566
+ currency: "EUR",
6567
+ customerName: freeformSelection.customerName.trim(),
6568
+ customerEmail: freeformSelection.customerEmail.trim(),
6569
+ customerPhone: freeformSelection.customerPhone?.trim(),
6570
+ comment: freeformSelection.comment?.trim(),
6571
+ locale,
6572
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6573
+ ...(cardToken && { cardToken }),
6574
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6575
+ }
6576
+ : {
6577
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
6578
+ organizationId: config.organizationId,
6579
+ amount: Math.round(totalAmount),
6580
+ currency: "EUR",
6581
+ participants: formData.participants.filter((p) => p.name?.trim()),
6582
+ discountCode: discountCode?.code,
6583
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6584
+ customerName: formData.customerName?.trim(),
6585
+ customerEmail: formData.customerEmail?.trim(),
6586
+ customerPhone: formData.customerPhone?.trim(),
6587
+ comment: formData.comment?.trim(),
6588
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6589
+ ...(cardToken && { cardToken }),
6590
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6591
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6592
+ }),
6632
6593
  });
6633
6594
  const handleCardPayment = async () => {
6634
6595
  if (!mollieRef.current)
@@ -6646,7 +6607,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6646
6607
  setPaymentError(tokenError?.message || t("error.createPayment"));
6647
6608
  return;
6648
6609
  }
6649
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6610
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6611
+ ? "/booking/create-freeform-mollie-payment"
6612
+ : "/booking/create-mollie-payment"), {
6650
6613
  method: "POST",
6651
6614
  headers: createApiHeaders(config, locale),
6652
6615
  body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
@@ -6682,7 +6645,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6682
6645
  setPaymentError(validationError);
6683
6646
  return;
6684
6647
  }
6685
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6648
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6649
+ ? "/booking/create-freeform-mollie-payment"
6650
+ : "/booking/create-mollie-payment"), {
6686
6651
  method: "POST",
6687
6652
  headers: createApiHeaders(config, locale),
6688
6653
  body: JSON.stringify(createRequestBody(config, buildRequestData())),
@@ -6710,7 +6675,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6710
6675
  ? t("button.processingPayment")
6711
6676
  : selectedMethodData
6712
6677
  ? t("payment.payWithMethod", { method: selectedMethodData.description })
6713
- : totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6678
+ : !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6714
6679
  ? t("button.depositAndBook")
6715
6680
  : t("button.bookNow");
6716
6681
  const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
@@ -6852,68 +6817,252 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6852
6817
  }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("var(--bw-surface-color)"), " ", buttonLabel] })) : (buttonLabel) }))] }));
6853
6818
  }
6854
6819
 
6855
- // Inner component that uses the Stripe hooks
6856
- function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
6857
- const t = useTranslations();
6858
- const participantCount = formData.participants.filter((p) => p.name.trim()).length;
6859
- const fullAmount = eventDetails.price * participantCount;
6860
- const submitLabel = totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
6861
- return (jsxRuntime.jsx(StripeElementsPaymentForm, { onValidate: () => {
6862
- const availableSpots = eventDetails.availableSpots || 0;
6863
- if (participantCount > availableSpots) {
6864
- return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
6865
- }
6866
- if (participantCount === 0) {
6867
- return t("payment.needOneParticipant");
6868
- }
6869
- return null;
6870
- }, onSuccess: (paymentIntent) => onSuccess({
6871
- paymentIntent,
6872
- }), onError: onError, paymentElementOptions: {
6873
- layout: "accordion",
6874
- defaultValues: {
6875
- billingDetails: {
6876
- name: formData.customerName,
6877
- email: formData.customerEmail,
6878
- phone: formData.customerPhone,
6879
- },
6880
- },
6881
- }, submitContent: jsxRuntime.jsx(jsxRuntime.Fragment, { children: submitLabel }), loadingContent: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
6882
- }
6883
- function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
6884
- const t = useTranslations();
6885
- const { locale } = useLocale();
6886
- const [clientSecret, setClientSecret] = React.useState(null);
6887
- const [paymentIntentId, setPaymentIntentId] = React.useState(null);
6888
- const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = React.useState(false);
6889
- const [paymentError, setPaymentError] = React.useState(null);
6890
- const storageKey = typeof window !== "undefined"
6891
- ? `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
6892
- : "";
6893
- const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
6894
- function loadPersistedPaymentIntent() {
6895
- if (typeof window === "undefined" || !storageKey)
6896
- return null;
6897
- try {
6898
- const raw = window.localStorage.getItem(storageKey);
6899
- if (!raw)
6900
- return null;
6901
- const parsed = JSON.parse(raw);
6902
- if (!parsed?.id || !parsed?.ts)
6903
- return null;
6904
- if (Date.now() - parsed.ts > PAYMENT_INTENT_TTL) {
6905
- window.localStorage.removeItem(storageKey);
6906
- return null;
6907
- }
6908
- return parsed.id;
6909
- }
6910
- catch {
6911
- return null;
6912
- }
6820
+ var V3_URL = 'https://js.stripe.com/v3';
6821
+ var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
6822
+ var EXISTING_SCRIPT_MESSAGE = 'loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used';
6823
+ var findScript = function findScript() {
6824
+ var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
6825
+
6826
+ for (var i = 0; i < scripts.length; i++) {
6827
+ var script = scripts[i];
6828
+
6829
+ if (!V3_URL_REGEX.test(script.src)) {
6830
+ continue;
6913
6831
  }
6914
- function persistPaymentIntent(id) {
6915
- if (typeof window === "undefined" || !storageKey || !id)
6916
- return;
6832
+
6833
+ return script;
6834
+ }
6835
+
6836
+ return null;
6837
+ };
6838
+
6839
+ var injectScript = function injectScript(params) {
6840
+ var queryString = '';
6841
+ var script = document.createElement('script');
6842
+ script.src = "".concat(V3_URL).concat(queryString);
6843
+ var headOrBody = document.head || document.body;
6844
+
6845
+ if (!headOrBody) {
6846
+ throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
6847
+ }
6848
+
6849
+ headOrBody.appendChild(script);
6850
+ return script;
6851
+ };
6852
+
6853
+ var registerWrapper = function registerWrapper(stripe, startTime) {
6854
+ if (!stripe || !stripe._registerWrapper) {
6855
+ return;
6856
+ }
6857
+
6858
+ stripe._registerWrapper({
6859
+ name: 'stripe-js',
6860
+ version: "4.6.0",
6861
+ startTime: startTime
6862
+ });
6863
+ };
6864
+
6865
+ var stripePromise = null;
6866
+ var onErrorListener = null;
6867
+ var onLoadListener = null;
6868
+
6869
+ var onError = function onError(reject) {
6870
+ return function () {
6871
+ reject(new Error('Failed to load Stripe.js'));
6872
+ };
6873
+ };
6874
+
6875
+ var onLoad = function onLoad(resolve, reject) {
6876
+ return function () {
6877
+ if (window.Stripe) {
6878
+ resolve(window.Stripe);
6879
+ } else {
6880
+ reject(new Error('Stripe.js not available'));
6881
+ }
6882
+ };
6883
+ };
6884
+
6885
+ var loadScript = function loadScript(params) {
6886
+ // Ensure that we only attempt to load Stripe.js at most once
6887
+ if (stripePromise !== null) {
6888
+ return stripePromise;
6889
+ }
6890
+
6891
+ stripePromise = new Promise(function (resolve, reject) {
6892
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
6893
+ // Resolve to null when imported server side. This makes the module
6894
+ // safe to import in an isomorphic code base.
6895
+ resolve(null);
6896
+ return;
6897
+ }
6898
+
6899
+ if (window.Stripe) {
6900
+ resolve(window.Stripe);
6901
+ return;
6902
+ }
6903
+
6904
+ try {
6905
+ var script = findScript();
6906
+
6907
+ if (script && params) ; else if (!script) {
6908
+ script = injectScript(params);
6909
+ } else if (script && onLoadListener !== null && onErrorListener !== null) {
6910
+ var _script$parentNode;
6911
+
6912
+ // remove event listeners
6913
+ script.removeEventListener('load', onLoadListener);
6914
+ script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
6915
+ // reload script to trigger 'load' event
6916
+
6917
+ (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
6918
+ script = injectScript(params);
6919
+ }
6920
+
6921
+ onLoadListener = onLoad(resolve, reject);
6922
+ onErrorListener = onError(reject);
6923
+ script.addEventListener('load', onLoadListener);
6924
+ script.addEventListener('error', onErrorListener);
6925
+ } catch (error) {
6926
+ reject(error);
6927
+ return;
6928
+ }
6929
+ }); // Resets stripePromise on error
6930
+
6931
+ return stripePromise["catch"](function (error) {
6932
+ stripePromise = null;
6933
+ return Promise.reject(error);
6934
+ });
6935
+ };
6936
+ var initStripe = function initStripe(maybeStripe, args, startTime) {
6937
+ if (maybeStripe === null) {
6938
+ return null;
6939
+ }
6940
+
6941
+ var stripe = maybeStripe.apply(undefined, args);
6942
+ registerWrapper(stripe, startTime);
6943
+ return stripe;
6944
+ }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
6945
+
6946
+ var stripePromise$1;
6947
+ var loadCalled = false;
6948
+
6949
+ var getStripePromise = function getStripePromise() {
6950
+ if (stripePromise$1) {
6951
+ return stripePromise$1;
6952
+ }
6953
+
6954
+ stripePromise$1 = loadScript(null)["catch"](function (error) {
6955
+ // clear cache on error
6956
+ stripePromise$1 = null;
6957
+ return Promise.reject(error);
6958
+ });
6959
+ return stripePromise$1;
6960
+ }; // Execute our own script injection after a tick to give users time to do their
6961
+ // own script injection.
6962
+
6963
+
6964
+ Promise.resolve().then(function () {
6965
+ return getStripePromise();
6966
+ })["catch"](function (error) {
6967
+ if (!loadCalled) {
6968
+ console.warn(error);
6969
+ }
6970
+ });
6971
+ var loadStripe = function loadStripe() {
6972
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
6973
+ args[_key] = arguments[_key];
6974
+ }
6975
+
6976
+ loadCalled = true;
6977
+ var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
6978
+
6979
+ return getStripePromise().then(function (maybeStripe) {
6980
+ return initStripe(maybeStripe, args, startTime);
6981
+ });
6982
+ };
6983
+
6984
+ // Inner component that uses the Stripe hooks
6985
+ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
6986
+ const t = useTranslations();
6987
+ const isFreeform = Boolean(freeformSelection);
6988
+ const participantCount = isFreeform
6989
+ ? 1
6990
+ : formData.participants.filter((p) => p.name.trim()).length;
6991
+ const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
6992
+ const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
6993
+ return (jsxRuntime.jsx(StripeElementsPaymentForm, { onValidate: () => {
6994
+ if (isFreeform) {
6995
+ return null;
6996
+ }
6997
+ const availableSpots = eventDetails.availableSpots || 0;
6998
+ if (participantCount > availableSpots) {
6999
+ return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
7000
+ }
7001
+ if (participantCount === 0) {
7002
+ return t("payment.needOneParticipant");
7003
+ }
7004
+ return null;
7005
+ }, onSuccess: (paymentIntent) => onSuccess({
7006
+ paymentIntent,
7007
+ }), onError: onError, paymentElementOptions: {
7008
+ layout: "accordion",
7009
+ defaultValues: {
7010
+ billingDetails: {
7011
+ name: formData.customerName,
7012
+ email: formData.customerEmail,
7013
+ phone: formData.customerPhone,
7014
+ },
7015
+ },
7016
+ }, submitContent: jsxRuntime.jsx(jsxRuntime.Fragment, { children: submitLabel }), loadingContent: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
7017
+ }
7018
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
7019
+ const t = useTranslations();
7020
+ const { locale } = useLocale();
7021
+ const [clientSecret, setClientSecret] = React.useState(null);
7022
+ const [paymentIntentId, setPaymentIntentId] = React.useState(null);
7023
+ const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = React.useState(false);
7024
+ const [paymentError, setPaymentError] = React.useState(null);
7025
+ const stripePromise = React.useMemo(() => {
7026
+ if (!systemConfig?.stripePublishableKey) {
7027
+ return null;
7028
+ }
7029
+ const stripeOptions = {
7030
+ locale: locale,
7031
+ };
7032
+ if (systemConfig.connectedAccountId) {
7033
+ stripeOptions.stripeAccount = systemConfig.connectedAccountId;
7034
+ }
7035
+ return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
7036
+ }, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
7037
+ const storageKey = typeof window !== "undefined"
7038
+ ? freeformSelection
7039
+ ? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
7040
+ : `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
7041
+ : "";
7042
+ const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
7043
+ function loadPersistedPaymentIntent() {
7044
+ if (typeof window === "undefined" || !storageKey)
7045
+ return null;
7046
+ try {
7047
+ const raw = window.localStorage.getItem(storageKey);
7048
+ if (!raw)
7049
+ return null;
7050
+ const parsed = JSON.parse(raw);
7051
+ if (!parsed?.id || !parsed?.ts)
7052
+ return null;
7053
+ if (Date.now() - parsed.ts > PAYMENT_INTENT_TTL) {
7054
+ window.localStorage.removeItem(storageKey);
7055
+ return null;
7056
+ }
7057
+ return parsed.id;
7058
+ }
7059
+ catch {
7060
+ return null;
7061
+ }
7062
+ }
7063
+ function persistPaymentIntent(id) {
7064
+ if (typeof window === "undefined" || !storageKey || !id)
7065
+ return;
6917
7066
  try {
6918
7067
  const payload = { id, ts: Date.now() };
6919
7068
  window.localStorage.setItem(storageKey, JSON.stringify(payload));
@@ -6943,47 +7092,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6943
7092
  }, [paymentIntentId]);
6944
7093
  React.useEffect(() => {
6945
7094
  const createStripePayment = async () => {
6946
- if (!systemConfig || !eventDetails || !formData.participants?.length) {
7095
+ if (!systemConfig || !eventDetails) {
7096
+ return;
7097
+ }
7098
+ if (freeformSelection) {
7099
+ if (!freeformSelection.customerEmail?.trim() ||
7100
+ !freeformSelection.customerName?.trim() ||
7101
+ !freeformSelection.start ||
7102
+ !freeformSelection.end) {
7103
+ return;
7104
+ }
7105
+ }
7106
+ else if (!formData.participants?.length) {
6947
7107
  return;
6948
7108
  }
6949
7109
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6950
- if (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim()) {
7110
+ if (!freeformSelection &&
7111
+ (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
6951
7112
  return;
6952
7113
  }
6953
7114
  setIsCreatingPaymentIntent(true);
6954
7115
  setPaymentError(null);
6955
7116
  try {
6956
- const requestData = {
6957
- eventInstanceId: config.eventInstanceId || eventDetails.id,
6958
- organizationId: config.organizationId,
6959
- amount: Math.round(totalAmount),
6960
- currency: "eur",
6961
- participants: formData.participants.filter((p) => p.name?.trim()),
6962
- discountCode: discountCode?.code,
6963
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6964
- customerName: formData.customerName?.trim(),
6965
- customerEmail: formData.customerEmail?.trim(),
6966
- customerPhone: formData.customerPhone?.trim(),
6967
- comment: formData.comment?.trim(),
6968
- ...(paymentIntentId && { paymentIntentId }),
6969
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6970
- };
6971
- if (!requestData.eventInstanceId) {
6972
- throw new Error("Event instance ID is required");
6973
- }
7117
+ const endpoint = freeformSelection
7118
+ ? "/booking/create-freeform-stripe-payment"
7119
+ : "/booking/create-stripe-payment";
7120
+ const requestData = freeformSelection
7121
+ ? {
7122
+ eventTypeId: freeformSelection.eventTypeId,
7123
+ organizationId: config.organizationId,
7124
+ start: freeformSelection.start,
7125
+ end: freeformSelection.end,
7126
+ requestedUnits: freeformSelection.requestedUnits,
7127
+ amount: Math.round(totalAmount),
7128
+ currency: "eur",
7129
+ customerName: freeformSelection.customerName.trim(),
7130
+ customerEmail: freeformSelection.customerEmail.trim(),
7131
+ customerPhone: freeformSelection.customerPhone?.trim(),
7132
+ comment: freeformSelection.comment?.trim(),
7133
+ locale,
7134
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7135
+ ...(paymentIntentId && { paymentIntentId }),
7136
+ }
7137
+ : {
7138
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
7139
+ organizationId: config.organizationId,
7140
+ amount: Math.round(totalAmount),
7141
+ currency: "eur",
7142
+ participants: formData.participants.filter((p) => p.name?.trim()),
7143
+ discountCode: discountCode?.code,
7144
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7145
+ customerName: formData.customerName?.trim(),
7146
+ customerEmail: formData.customerEmail?.trim(),
7147
+ customerPhone: formData.customerPhone?.trim(),
7148
+ comment: formData.comment?.trim(),
7149
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7150
+ ...(paymentIntentId && { paymentIntentId }),
7151
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7152
+ };
6974
7153
  if (!requestData.organizationId) {
6975
7154
  throw new Error("Organization ID is required");
6976
7155
  }
6977
- if (!requestData.amount || requestData.amount <= 0) {
7156
+ if (requestData.amount === undefined || requestData.amount < 0) {
6978
7157
  throw new Error("Valid amount is required");
6979
7158
  }
6980
- if (!requestData.participants || requestData.participants.length === 0) {
7159
+ if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
7160
+ throw new Error("Event instance ID is required");
7161
+ }
7162
+ if (!freeformSelection &&
7163
+ "participants" in requestData &&
7164
+ (!requestData.participants || requestData.participants.length === 0)) {
6981
7165
  throw new Error("At least one participant is required");
6982
7166
  }
6983
7167
  if (!requestData.customerEmail) {
6984
7168
  throw new Error("Customer email is required");
6985
7169
  }
6986
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
7170
+ const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
6987
7171
  method: "POST",
6988
7172
  headers: createApiHeaders(config, locale),
6989
7173
  body: JSON.stringify(createRequestBody(config, requestData)),
@@ -7028,13 +7212,17 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7028
7212
  giftCards,
7029
7213
  config,
7030
7214
  upsellSelections,
7215
+ freeformSelection,
7216
+ locale,
7031
7217
  ]);
7032
7218
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
7033
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7219
+ const isFullyCoveredByGiftCards = freeformSelection
7220
+ ? false
7221
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7034
7222
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
7035
7223
  return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
7036
7224
  }
7037
- if (isCreatingPaymentIntent || !clientSecret) {
7225
+ if (isCreatingPaymentIntent || !clientSecret || !stripePromise) {
7038
7226
  return (jsxRuntime.jsxs("div", { style: {
7039
7227
  display: "flex",
7040
7228
  alignItems: "center",
@@ -7069,7 +7257,50 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7069
7257
  setPaymentIntentId(null);
7070
7258
  setClientSecret(null);
7071
7259
  onSuccess(result);
7072
- }, onError: onError }) }));
7260
+ }, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
7261
+ }
7262
+
7263
+ const HOLD_DURATION_SECONDS = 15 * 60;
7264
+ /**
7265
+ * Shows the remaining time on the 15-minute slot hold that the server places on
7266
+ * the pending order while the customer completes checkout. The timer is started
7267
+ * client-side when the payment step opens — matching the moment the pending
7268
+ * order (and its `holdExpiresAt`) is created server-side.
7269
+ */
7270
+ function HoldCountdown({ onExpire }) {
7271
+ const t = useTranslations();
7272
+ const [remaining, setRemaining] = React.useState(HOLD_DURATION_SECONDS);
7273
+ React.useEffect(() => {
7274
+ const startedAt = Date.now();
7275
+ const interval = setInterval(() => {
7276
+ const elapsed = Math.floor((Date.now() - startedAt) / 1000);
7277
+ const left = Math.max(0, HOLD_DURATION_SECONDS - elapsed);
7278
+ setRemaining(left);
7279
+ if (left <= 0) {
7280
+ clearInterval(interval);
7281
+ onExpire?.();
7282
+ }
7283
+ }, 1000);
7284
+ return () => clearInterval(interval);
7285
+ }, [onExpire]);
7286
+ const minutes = Math.floor(remaining / 60);
7287
+ const seconds = remaining % 60;
7288
+ const formatted = `${minutes}:${String(seconds).padStart(2, "0")}`;
7289
+ const isLow = remaining <= 60;
7290
+ return (jsxRuntime.jsxs("div", { style: {
7291
+ display: "flex",
7292
+ alignItems: "center",
7293
+ gap: "8px",
7294
+ padding: "10px 14px",
7295
+ borderRadius: "var(--bw-border-radius)",
7296
+ border: `1px solid ${isLow ? "var(--bw-error-color)" : "var(--bw-border-color)"}`,
7297
+ backgroundColor: "var(--bw-surface-color)",
7298
+ color: isLow ? "var(--bw-error-color)" : "var(--bw-text-color)",
7299
+ fontFamily: "var(--bw-font-family)",
7300
+ fontSize: "14px",
7301
+ }, children: [jsxRuntime.jsx(IconClock, {}), jsxRuntime.jsx("span", { children: remaining > 0
7302
+ ? t("hold.reservedFor", { time: formatted })
7303
+ : t("hold.expired") })] }));
7073
7304
  }
7074
7305
 
7075
7306
  const FLAG_EMOJIS = {
@@ -11483,6 +11714,11 @@ const buttonStyles = {
11483
11714
  color: "var(--bw-text-muted)",
11484
11715
  border: "1px solid var(--bw-border-color)",
11485
11716
  },
11717
+ // Size variants
11718
+ small: {
11719
+ padding: "8px 12px",
11720
+ fontSize: "13px",
11721
+ },
11486
11722
  // Full width modifier
11487
11723
  fullWidth: {
11488
11724
  width: "100%",
@@ -11555,6 +11791,12 @@ const sectionStyles = {
11555
11791
  // TEXT
11556
11792
  // ============================================
11557
11793
  const textStyles = {
11794
+ body: {
11795
+ fontSize: "14px",
11796
+ color: "var(--bw-text-color)",
11797
+ fontFamily: "var(--bw-font-family)",
11798
+ lineHeight: 1.5,
11799
+ },
11558
11800
  muted: {
11559
11801
  fontSize: "14px",
11560
11802
  color: "var(--bw-text-muted)",
@@ -11624,7 +11866,7 @@ const sectionHeaderStyles$1 = sectionStyles.header;
11624
11866
  const labelStyles$1 = formStyles.label;
11625
11867
  const inputStyles$1 = formStyles.input;
11626
11868
  const errorTextStyles$1 = formStyles.error;
11627
- function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
11869
+ function BookingForm({ config, eventDetails, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
11628
11870
  const t$1 = useTranslations();
11629
11871
  const { locale } = useLocale();
11630
11872
  const timezone = useTimezone();
@@ -11824,7 +12066,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11824
12066
  newTotal: appliedDiscountCode.newTotal,
11825
12067
  }
11826
12068
  : null;
11827
- const hasPaymentProvider = Boolean(stripePromise || systemConfig?.paymentProvider === "mollie");
12069
+ const hasPaymentProvider = systemConfig?.paymentProvider === "stripe" || systemConfig?.paymentProvider === "mollie";
11828
12070
  const handleVoucherValidated = React.useCallback((voucher, _error) => {
11829
12071
  if (voucher) {
11830
12072
  setAppliedVouchers((prev) => [...prev, voucher]);
@@ -12090,7 +12332,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
12090
12332
  ...inputStyles$1,
12091
12333
  resize: "vertical",
12092
12334
  minHeight: "80px",
12093
- } })] })] })] })), checkoutStep === "payment" && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [jsxRuntime.jsxs("div", { style: cardStyles$1, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: "16px" }, children: t$1("summary.title") }), jsxRuntime.jsxs("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("booking.price") }), jsxRuntime.jsxs("div", { style: {
12335
+ } })] })] })] })), checkoutStep === "payment" && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [jsxRuntime.jsx(HoldCountdown, {}), jsxRuntime.jsxs("div", { style: cardStyles$1, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: "16px" }, children: t$1("summary.title") }), jsxRuntime.jsxs("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("booking.price") }), jsxRuntime.jsxs("div", { style: {
12094
12336
  color: "var(--bw-text-color)",
12095
12337
  fontWeight: 500,
12096
12338
  fontFamily: "var(--bw-font-family)",
@@ -12194,7 +12436,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
12194
12436
  fontFamily: "var(--bw-font-family)",
12195
12437
  marginTop: "8px",
12196
12438
  textAlign: "right",
12197
- }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), depositsOnlyBlocked && (jsxRuntime.jsx("div", { style: cardStyles$1, children: jsxRuntime.jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.depositNotConfigured") }) })), !hasPaymentProvider && !depositsOnlyBlocked && (jsxRuntime.jsx("div", { style: cardStyles$1, children: jsxRuntime.jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.paymentUnavailable") }) })), hasPaymentProvider && !depositsOnlyBlocked && (systemConfig?.paymentProvider === "mollie" ? (jsxRuntime.jsxs("div", { style: cardStyles$1, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] })) : (jsxRuntime.jsxs("div", { style: cardStyles$1, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripePromise: stripePromise, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] })))] }))] }) }));
12439
+ }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), depositsOnlyBlocked && (jsxRuntime.jsx("div", { style: cardStyles$1, children: jsxRuntime.jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.depositNotConfigured") }) })), !hasPaymentProvider && !depositsOnlyBlocked && (jsxRuntime.jsx("div", { style: cardStyles$1, children: jsxRuntime.jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.paymentUnavailable") }) })), hasPaymentProvider && !depositsOnlyBlocked && (systemConfig?.paymentProvider === "mollie" ? (jsxRuntime.jsxs("div", { style: cardStyles$1, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] })) : (jsxRuntime.jsxs("div", { style: cardStyles$1, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] })))] }))] }) }));
12198
12440
  }
12199
12441
 
12200
12442
  /**
@@ -13275,20 +13517,22 @@ const sectionHeaderStyles = sectionStyles.header;
13275
13517
  const labelStyles = formStyles.label;
13276
13518
  const inputStyles = formStyles.input;
13277
13519
  const errorTextStyles = formStyles.error;
13278
- function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13520
+ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13279
13521
  const t = useTranslations();
13280
13522
  const { locale } = useLocale();
13281
13523
  const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
13282
13524
  // Form state
13283
- // Show voucher type selection if both monetary and event vouchers are allowed
13284
- // The event type selection will handle the case when no event types are available
13285
13525
  const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
13286
13526
  const canUseEventVoucherType = voucherConfig.allowEventVouchers;
13287
- const showVoucherTypeSelection = canUseMonetaryVoucherType && canUseEventVoucherType && !isLoadingEventTypes;
13527
+ const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
13528
+ const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
13529
+ const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
13288
13530
  const [voucherType, setVoucherType] = React.useState(canUseMonetaryVoucherType ? "monetary" : "event");
13289
13531
  const [monetaryAmount, setMonetaryAmount] = React.useState(voucherConfig.monetaryPresets[2] || 10000);
13290
13532
  const [selectedEventTypeId, setSelectedEventTypeId] = React.useState(null);
13291
13533
  const [eventQuantity, setEventQuantity] = React.useState(1);
13534
+ const [selectedCategoryId, setSelectedCategoryId] = React.useState(null);
13535
+ const [categoryAmount, setCategoryAmount] = React.useState(10000);
13292
13536
  const [recipientName, setRecipientName] = React.useState("");
13293
13537
  const [message, setMessage] = React.useState("");
13294
13538
  const [customerName, setCustomerName] = React.useState("");
@@ -13303,6 +13547,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13303
13547
  const [stripeAppearance, setStripeAppearance] = React.useState(null);
13304
13548
  // Initialize Stripe
13305
13549
  React.useEffect(() => {
13550
+ if (!isOpen)
13551
+ return;
13306
13552
  if (paymentProvider !== "stripe")
13307
13553
  return;
13308
13554
  if (systemConfig?.stripePublishableKey && !stripePromise) {
@@ -13314,7 +13560,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13314
13560
  }
13315
13561
  setStripePromise(loadStripe(systemConfig.stripePublishableKey, stripeOptions));
13316
13562
  }
13317
- }, [paymentProvider, systemConfig, locale, stripePromise]);
13563
+ }, [isOpen, paymentProvider, systemConfig, locale, stripePromise]);
13318
13564
  // Initialize Stripe appearance
13319
13565
  React.useEffect(() => {
13320
13566
  const container = document.querySelector(".booking-widget-container");
@@ -13346,6 +13592,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13346
13592
  if (voucherType === "monetary") {
13347
13593
  return monetaryAmount;
13348
13594
  }
13595
+ else if (voucherType === "category") {
13596
+ return categoryAmount;
13597
+ }
13349
13598
  else {
13350
13599
  const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
13351
13600
  if (eventType) {
@@ -13353,7 +13602,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13353
13602
  }
13354
13603
  return 0;
13355
13604
  }
13356
- }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
13605
+ }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
13357
13606
  const selectedEventType = React.useMemo(() => {
13358
13607
  return eventTypes.find((et) => et.id === selectedEventTypeId);
13359
13608
  }, [eventTypes, selectedEventTypeId]);
@@ -13362,6 +13611,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13362
13611
  voucherType,
13363
13612
  selectedEventTypeId,
13364
13613
  eventQuantity,
13614
+ selectedCategoryId,
13615
+ categoryAmount,
13365
13616
  recipientName: recipientName.trim(),
13366
13617
  message: message.trim(),
13367
13618
  customerName: customerName.trim(),
@@ -13371,6 +13622,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13371
13622
  voucherType,
13372
13623
  selectedEventTypeId,
13373
13624
  eventQuantity,
13625
+ selectedCategoryId,
13626
+ categoryAmount,
13374
13627
  recipientName,
13375
13628
  message,
13376
13629
  customerName,
@@ -13389,24 +13642,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13389
13642
  return false;
13390
13643
  if (voucherType === "event" && !selectedEventTypeId)
13391
13644
  return false;
13645
+ if (voucherType === "category" && !selectedCategoryId)
13646
+ return false;
13392
13647
  return true;
13393
- }, [customerName, customerEmail, acceptTerms, totalAmount, voucherConfig, voucherType, selectedEventTypeId]);
13648
+ }, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
13394
13649
  const defaultMonetaryAmount = React.useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
13395
13650
  const handleVoucherTypeChange = React.useCallback((nextType) => {
13396
13651
  setVoucherType(nextType);
13397
13652
  setPaymentError(null);
13398
13653
  if (nextType === "event") {
13399
- // Keep voucher type mutually exclusive: entering event flow resets amount selection state.
13400
13654
  setMonetaryAmount(defaultMonetaryAmount);
13655
+ setSelectedCategoryId(null);
13656
+ setCategoryAmount(10000);
13401
13657
  if (eventTypes.length === 1) {
13402
13658
  setSelectedEventTypeId(eventTypes[0]?.id ?? null);
13403
13659
  }
13404
13660
  return;
13405
13661
  }
13406
- // Keep voucher type mutually exclusive: entering amount flow clears event selection.
13662
+ if (nextType === "category") {
13663
+ setMonetaryAmount(defaultMonetaryAmount);
13664
+ setSelectedEventTypeId(null);
13665
+ setEventQuantity(1);
13666
+ if (categories.length === 1) {
13667
+ setSelectedCategoryId(categories[0]?.id ?? null);
13668
+ }
13669
+ return;
13670
+ }
13407
13671
  setSelectedEventTypeId(null);
13408
13672
  setEventQuantity(1);
13409
- }, [defaultMonetaryAmount, eventTypes]);
13673
+ setSelectedCategoryId(null);
13674
+ setCategoryAmount(10000);
13675
+ }, [defaultMonetaryAmount, eventTypes, categories]);
13410
13676
  React.useEffect(() => {
13411
13677
  if (!canUseMonetaryVoucherType && voucherType === "monetary") {
13412
13678
  setVoucherType("event");
@@ -13447,9 +13713,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13447
13713
  organizationId: config.organizationId,
13448
13714
  amount: totalAmount,
13449
13715
  currency: "eur",
13450
- voucherType,
13451
- monetaryValue: voucherType === "monetary" ? totalAmount : undefined,
13716
+ voucherType: voucherType === "category" ? "monetary" : voucherType,
13717
+ monetaryValue: voucherType === "monetary" ? totalAmount
13718
+ : voucherType === "category" ? categoryAmount
13719
+ : undefined,
13452
13720
  eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
13721
+ eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
13453
13722
  eventAmount: voucherType === "event" ? eventQuantity : undefined,
13454
13723
  recipientName: recipientName.trim() || undefined,
13455
13724
  message: message.trim() || undefined,
@@ -13640,7 +13909,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13640
13909
  fontSize: "13px",
13641
13910
  color: "var(--bw-text-muted)",
13642
13911
  marginTop: "4px",
13643
- }, children: t("voucher.eventTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsxRuntime.jsx("div", { style: {
13912
+ }, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxRuntime.jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
13913
+ flex: 1,
13914
+ padding: "16px",
13915
+ borderRadius: "var(--bw-border-radius)",
13916
+ border: voucherType === "category"
13917
+ ? "2px solid var(--bw-highlight-color)"
13918
+ : "1px solid var(--bw-border-color)",
13919
+ backgroundColor: voucherType === "category"
13920
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
13921
+ : "var(--bw-surface-color)",
13922
+ cursor: "pointer",
13923
+ fontFamily: "var(--bw-font-family)",
13924
+ transition: "all 0.2s ease",
13925
+ textAlign: "center",
13926
+ }, children: [jsxRuntime.jsx("div", { style: {
13927
+ fontWeight: 600,
13928
+ color: voucherType === "category"
13929
+ ? "var(--bw-highlight-color)"
13930
+ : "var(--bw-text-color)",
13931
+ }, children: t("voucher.categoryType") }), jsxRuntime.jsx("div", { style: {
13932
+ fontSize: "13px",
13933
+ color: "var(--bw-text-muted)",
13934
+ marginTop: "4px",
13935
+ }, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsxRuntime.jsx("div", { style: {
13644
13936
  display: "grid",
13645
13937
  gridTemplateColumns: "repeat(3, 1fr)",
13646
13938
  gap: "8px",
@@ -13695,7 +13987,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13695
13987
  fontSize: "18px",
13696
13988
  color: "var(--bw-highlight-color)",
13697
13989
  fontFamily: "var(--bw-font-family)",
13698
- }, children: formatCurrency(totalAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsxRuntime.jsx("p", { style: {
13990
+ }, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), jsxRuntime.jsx("div", { style: { marginTop: "12px" }, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), jsxRuntime.jsxs("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
13991
+ ...inputStyles,
13992
+ cursor: categories.length === 1 ? "not-allowed" : "pointer",
13993
+ }, children: [categories.length > 1 && (jsxRuntime.jsx("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), jsxRuntime.jsx("div", { style: {
13994
+ display: "grid",
13995
+ gridTemplateColumns: "repeat(3, 1fr)",
13996
+ gap: "8px",
13997
+ marginTop: "4px",
13998
+ }, children: voucherConfig.monetaryPresets.map((preset) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
13999
+ padding: "10px",
14000
+ borderRadius: "var(--bw-border-radius)",
14001
+ border: categoryAmount === preset
14002
+ ? "2px solid var(--bw-highlight-color)"
14003
+ : "1px solid var(--bw-border-color)",
14004
+ backgroundColor: categoryAmount === preset
14005
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
14006
+ : "var(--bw-surface-color)",
14007
+ cursor: "pointer",
14008
+ fontFamily: "var(--bw-font-family)",
14009
+ fontWeight: 600,
14010
+ fontSize: "14px",
14011
+ color: categoryAmount === preset
14012
+ ? "var(--bw-highlight-color)"
14013
+ : "var(--bw-text-color)",
14014
+ transition: "all 0.2s ease",
14015
+ }, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsxRuntime.jsx("div", { style: {
14016
+ marginTop: "16px",
14017
+ padding: "12px",
14018
+ backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
14019
+ borderRadius: "var(--bw-border-radius)",
14020
+ border: "1px solid var(--bw-highlight-color)",
14021
+ }, children: jsxRuntime.jsxs("div", { style: {
14022
+ display: "flex",
14023
+ justifyContent: "space-between",
14024
+ alignItems: "center",
14025
+ }, children: [jsxRuntime.jsx("span", { style: {
14026
+ color: "var(--bw-text-color)",
14027
+ fontFamily: "var(--bw-font-family)",
14028
+ }, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsxRuntime.jsx("span", { style: {
14029
+ fontWeight: 700,
14030
+ fontSize: "18px",
14031
+ color: "var(--bw-highlight-color)",
14032
+ fontFamily: "var(--bw-font-family)",
14033
+ }, children: formatCurrency(categoryAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsxRuntime.jsx("p", { style: {
13699
14034
  fontSize: "13px",
13700
14035
  color: "var(--bw-text-muted)",
13701
14036
  fontFamily: "var(--bw-font-family)",
@@ -13728,7 +14063,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13728
14063
  fontFamily: "var(--bw-font-family)",
13729
14064
  }, children: voucherType === "monetary"
13730
14065
  ? t("voucher.monetaryVoucher")
13731
- : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxRuntime.jsxs("div", { style: {
14066
+ : voucherType === "category"
14067
+ ? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
14068
+ : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxRuntime.jsxs("div", { style: {
13732
14069
  fontSize: "13px",
13733
14070
  color: "var(--bw-text-muted)",
13734
14071
  fontFamily: "var(--bw-font-family)",
@@ -14004,7 +14341,7 @@ function VoucherAttachment({ onClick }) {
14004
14341
  }, children: t("voucher.buyAsGift") })] }));
14005
14342
  }
14006
14343
 
14007
- function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14344
+ function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14008
14345
  if (!voucherConfig?.enabled) {
14009
14346
  return null;
14010
14347
  }
@@ -14015,7 +14352,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
14015
14352
  justifyContent: "center",
14016
14353
  }, children: jsxRuntime.jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
14017
14354
  ? Math.min(...eventTypes.map((et) => et.maxPrice))
14018
- : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14355
+ : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, categories: categories, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14019
14356
  }
14020
14357
 
14021
14358
  // Helper function to preprocess markdown for underline support
@@ -14278,8 +14615,17 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14278
14615
  if (!item)
14279
14616
  return jsxRuntime.jsx("div", { style: { height: "34px" } }, i);
14280
14617
  const hasDiscount = item.basePrice > 0 && item.price < item.basePrice;
14281
- return (jsxRuntime.jsxs("div", { onClick: (e) => { e.stopPropagation(); onInstancePreview?.(item.id, eventType.id); }, onMouseEnter: (e) => { if (onInstancePreview)
14282
- e.currentTarget.style.backgroundColor = "var(--bw-border-color)"; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14618
+ const isRowBookable = item.bookingOpen && item.availableSpots > 0;
14619
+ return (jsxRuntime.jsxs("div", { onClick: (e) => {
14620
+ e.stopPropagation();
14621
+ if (isRowBookable) {
14622
+ onInstancePreview?.(item.id, eventType.id);
14623
+ }
14624
+ }, onMouseEnter: (e) => {
14625
+ if (onInstancePreview && isRowBookable) {
14626
+ e.currentTarget.style.backgroundColor = "var(--bw-highlight-subtle)";
14627
+ }
14628
+ }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14283
14629
  height: "34px",
14284
14630
  display: "grid",
14285
14631
  gridTemplateColumns: "auto 1fr auto 20px",
@@ -14288,12 +14634,13 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14288
14634
  width: "100%",
14289
14635
  fontSize: "13px",
14290
14636
  color: "var(--bw-text-muted)",
14291
- cursor: onInstancePreview ? "pointer" : "default",
14637
+ cursor: onInstancePreview && isRowBookable ? "pointer" : "default",
14292
14638
  borderRadius: "4px",
14293
14639
  padding: "0 2px",
14294
14640
  transition: "background 0.15s",
14295
14641
  boxSizing: "border-box",
14296
- }, children: [jsxRuntime.jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsxRuntime.jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsxRuntime.jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsxRuntime.jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.specialDescription ? (jsxRuntime.jsx(InfoBadge, { text: item.specialDescription })) : (jsxRuntime.jsx("span", {}))] }, item.id));
14642
+ opacity: isRowBookable ? 1 : 0.55,
14643
+ }, children: [jsxRuntime.jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsxRuntime.jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsxRuntime.jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsxRuntime.jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.isSpecial && item.specialDescription ? (jsxRuntime.jsx(InfoBadge, { text: item.specialDescription })) : (jsxRuntime.jsx("span", {}))] }, item.id));
14297
14644
  }) }), hasMoreDates && (jsxRuntime.jsx("button", { type: "button", onClick: (e) => {
14298
14645
  e.stopPropagation();
14299
14646
  onEventTypeSelect(eventType);
@@ -14382,6 +14729,74 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14382
14729
  }) }) })), jsxRuntime.jsx(PaymentLogosStrip, {}), jsxRuntime.jsx(EventTypeDetailsDialog, { isOpen: detailsDialogOpen, onClose: handleCloseDetails, eventType: selectedEventTypeForDetails, onEventTypeSelect: onEventTypeSelect })] }));
14383
14730
  }
14384
14731
 
14732
+ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName, onClose, }) {
14733
+ const t = useTranslations();
14734
+ const [name, setName] = React.useState("");
14735
+ const [email, setEmail] = React.useState("");
14736
+ const [phone, setPhone] = React.useState("");
14737
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
14738
+ const [error, setError] = React.useState(null);
14739
+ const [position, setPosition] = React.useState(null);
14740
+ const handleSubmit = async () => {
14741
+ setError(null);
14742
+ if (!name.trim() || !email.trim()) {
14743
+ setError(t("waitlist.requiredFields"));
14744
+ return;
14745
+ }
14746
+ setIsSubmitting(true);
14747
+ try {
14748
+ const response = await fetch(getApiUrl(apiBaseUrl, "/booking/waitlist"), {
14749
+ method: "POST",
14750
+ headers: { "Content-Type": "application/json" },
14751
+ body: JSON.stringify({
14752
+ organizationId,
14753
+ eventInstanceId,
14754
+ name: name.trim(),
14755
+ email: email.trim(),
14756
+ phone: phone.trim() || undefined,
14757
+ }),
14758
+ });
14759
+ if (!response.ok) {
14760
+ throw new Error("request_failed");
14761
+ }
14762
+ const data = (await response.json());
14763
+ setPosition(data.position ?? null);
14764
+ }
14765
+ catch {
14766
+ setError(t("waitlist.submitError"));
14767
+ }
14768
+ finally {
14769
+ setIsSubmitting(false);
14770
+ }
14771
+ };
14772
+ return (jsxRuntime.jsx("div", { style: {
14773
+ position: "fixed",
14774
+ inset: 0,
14775
+ zIndex: 1000,
14776
+ display: "flex",
14777
+ alignItems: "center",
14778
+ justifyContent: "center",
14779
+ backgroundColor: "rgba(15, 23, 42, 0.6)",
14780
+ padding: "16px",
14781
+ fontFamily: "var(--bw-font-family)",
14782
+ }, onClick: onClose, children: jsxRuntime.jsx("div", { style: {
14783
+ width: "100%",
14784
+ maxWidth: "420px",
14785
+ backgroundColor: "var(--bw-surface-color)",
14786
+ border: "1px solid var(--bw-border-color)",
14787
+ borderRadius: "var(--bw-border-radius)",
14788
+ padding: "24px",
14789
+ }, onClick: (e) => e.stopPropagation(), children: position !== null ? (jsxRuntime.jsxs("div", { style: { textAlign: "center" }, children: [jsxRuntime.jsx("h3", { style: {
14790
+ margin: "0 0 8px",
14791
+ color: "var(--bw-highlight-color)",
14792
+ fontSize: "18px",
14793
+ }, children: t("waitlist.successTitle") }), jsxRuntime.jsx("p", { style: mergeStyles(textStyles.body, { marginBottom: "20px" }), children: t("waitlist.successMessage", { position }) }), jsxRuntime.jsx("button", { type: "button", style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), onClick: onClose, children: t("waitlist.done") })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("h3", { style: {
14794
+ margin: "0 0 4px",
14795
+ color: "var(--bw-highlight-color)",
14796
+ fontSize: "18px",
14797
+ }, children: t("waitlist.title") }), jsxRuntime.jsx("p", { style: mergeStyles(textStyles.muted, { marginBottom: "16px" }), children: t("waitlist.subtitle", { name: eventName }) }), jsxRuntime.jsxs("div", { style: { marginBottom: "12px" }, children: [jsxRuntime.jsx("label", { style: formStyles.label, htmlFor: "wl-name", children: t("waitlist.name") }), jsxRuntime.jsx("input", { id: "wl-name", style: formStyles.input, value: name, onChange: (e) => setName(e.target.value) })] }), jsxRuntime.jsxs("div", { style: { marginBottom: "12px" }, children: [jsxRuntime.jsx("label", { style: formStyles.label, htmlFor: "wl-email", children: t("waitlist.email") }), jsxRuntime.jsx("input", { id: "wl-email", type: "email", style: formStyles.input, value: email, onChange: (e) => setEmail(e.target.value) })] }), jsxRuntime.jsxs("div", { style: { marginBottom: "12px" }, children: [jsxRuntime.jsx("label", { style: formStyles.label, htmlFor: "wl-phone", children: t("waitlist.phone") }), jsxRuntime.jsx("input", { id: "wl-phone", type: "tel", style: formStyles.input, value: phone, onChange: (e) => setPhone(e.target.value) })] }), error && jsxRuntime.jsx("p", { style: formStyles.error, children: error }), jsxRuntime.jsxs("div", { style: { display: "flex", gap: "8px", marginTop: "16px" }, children: [jsxRuntime.jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), onClick: onClose, disabled: isSubmitting, children: t("waitlist.cancel") }), jsxRuntime.jsx("button", { type: "button", style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), onClick: handleSubmit, disabled: isSubmitting, children: isSubmitting ? t("waitlist.submitting") : t("waitlist.join") })] })] })) }) }));
14798
+ }
14799
+
14385
14800
  const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
14386
14801
  const availabilityRatio = availableSpots / maxParticipants;
14387
14802
  if (availabilityRatio >= 0.6)
@@ -14506,12 +14921,13 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
14506
14921
  boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
14507
14922
  }, children: formatCurrency(price) }));
14508
14923
  };
14509
- function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, }) {
14924
+ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
14510
14925
  const t = useTranslations();
14511
14926
  const { locale } = useLocale();
14512
14927
  const timezone = useTimezone();
14513
14928
  const [selectedEventInstanceId, setSelectedEventInstanceId] = React.useState(null);
14514
14929
  const [openGroups, setOpenGroups] = React.useState(new Set());
14930
+ const [waitlistEvent, setWaitlistEvent] = React.useState(null);
14515
14931
  const getMonthPriceDisplayInfo = (minPrice) => {
14516
14932
  return getPriceDisplayInfo(minPrice, yearPrices, t);
14517
14933
  };
@@ -14672,116 +15088,127 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14672
15088
  fontFamily: "var(--bw-font-family)",
14673
15089
  }, children: t("instances.noAvailable") }), jsxRuntime.jsx("p", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("instances.noAvailableMessage") })] }) }) }));
14674
15090
  }
14675
- return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, footer: footerNav, children: jsxRuntime.jsxs("div", { style: { padding: "20px 10px" }, children: [jsxRuntime.jsx(FlowProgress, { currentStep: "date", hasUpsellsStep: hasUpsellsStep }), jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
14676
- const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
14677
- return (jsxRuntime.jsxs(React.Fragment, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsxRuntime.jsx("div", { style: { height: "1px", backgroundColor: "var(--bw-border-color)", margin: "4px 0" } })), jsxRuntime.jsx(Accordion, { title: label, priceInfo: jsxRuntime.jsx("div", { style: {
14678
- fontSize: "16px",
14679
- fontWeight: 500,
14680
- marginLeft: "auto",
14681
- padding: "4px 8px",
14682
- borderRadius: "var(--bw-border-radius-small)",
14683
- backgroundColor: monthPriceDisplayInfo
14684
- ? monthPriceDisplayInfo.backgroundColor
14685
- : "#14532d",
14686
- color: monthPriceDisplayInfo
14687
- ? monthPriceDisplayInfo.textColor
14688
- : "#4ade80",
14689
- boxShadow: monthPriceDisplayInfo
14690
- ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14691
- : undefined,
14692
- }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14693
- const availableSpots = event.maxParticipants - event.participantCount;
14694
- const isFullyBooked = availableSpots === 0;
14695
- const startDate = new Date(event.startTime);
14696
- const isPastEvent = today.toISOString() >= startDate.toISOString();
14697
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14698
- return (jsxRuntime.jsxs("div", { style: {
14699
- position: "relative",
14700
- border: "1px solid var(--bw-border-color)",
14701
- backgroundColor: "var(--bw-surface-color)",
14702
- borderRadius: "var(--bw-border-radius)",
14703
- padding: "16px 10px",
14704
- transition: "all 0.2s ease",
14705
- fontFamily: "var(--bw-font-family)",
14706
- opacity: isDisabled ? 0.3 : 1,
14707
- filter: isDisabled ? "grayscale(40%)" : "none",
14708
- cursor: isDisabled ? "not-allowed" : "pointer",
14709
- }, onClick: () => {
14710
- if (!isDisabled) {
14711
- handleEventInstanceSelect(event);
14712
- }
14713
- }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
14714
- position: "absolute",
14715
- inset: 0,
14716
- backgroundColor: "rgba(15, 23, 42, 0.8)",
14717
- borderRadius: "var(--bw-border-radius)",
14718
- display: "flex",
14719
- alignItems: "center",
14720
- justifyContent: "center",
14721
- }, children: jsxRuntime.jsx("div", { style: {
14722
- width: "32px",
14723
- height: "32px",
14724
- color: "var(--bw-highlight-color)",
14725
- opacity: 0.8,
14726
- fontSize: "32px",
14727
- }, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxRuntime.jsxs("div", { style: {
14728
- display: "flex",
14729
- justifyContent: "space-between",
14730
- width: "100%",
14731
- alignItems: "flex-start",
14732
- gap: "12px",
14733
- marginBottom: "4px",
14734
- }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsxRuntime.jsx("div", { style: {
14735
- fontSize: "16px",
14736
- transition: "all 0.2s ease",
14737
- borderRadius: "var(--bw-border-radius-small)",
14738
- borderTop: "4px solid var(--bw-border-color)",
14739
- border: "1px solid var(--bw-border-color)",
14740
- width: "40px",
14741
- height: "40px",
14742
- display: "flex",
14743
- alignItems: "center",
14744
- justifyContent: "center",
14745
- fontWeight: 700,
14746
- color: "var(--bw-text-color)",
14747
- backgroundColor: "var(--bw-background-color)",
14748
- }, children: startDate.getDate() }), jsxRuntime.jsxs("div", { style: {
14749
- fontSize: "16px",
14750
- color: "var(--bw-text-color)",
14751
- display: "flex",
14752
- flexDirection: "column",
14753
- alignItems: "flex-start",
14754
- justifyContent: "flex-start",
14755
- lineHeight: 1.25,
14756
- }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
14757
- formatWeekday(event.endTime, timezone, locale) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsxRuntime.jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsxRuntime.jsx("div", { children: formatWeekday(event.startTime, timezone, locale) ===
14758
- formatWeekday(event.endTime, timezone, locale) ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxRuntime.jsxs("span", { style: {
14759
- fontSize: "12px",
14760
- fontWeight: 400,
14761
- color: "var(--bw-text-muted)",
14762
- marginLeft: "6px",
14763
- backgroundColor: "rgba(0, 0, 0, 0.05)",
14764
- whiteSpace: "nowrap",
14765
- padding: "2px 6px",
14766
- borderRadius: "var(--bw-border-radius-small)",
14767
- }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsxRuntime.jsx("div", { style: {
14768
- textAlign: "right",
14769
- display: "flex",
14770
- flexDirection: "column",
14771
- alignItems: "flex-end",
14772
- }, children: jsxRuntime.jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsxRuntime.jsx("h4", { style: {
14773
- fontSize: "16px",
14774
- fontWeight: 600,
14775
- color: "var(--bw-text-color)",
14776
- lineHeight: 1.25,
14777
- margin: "0 0 2px 0",
14778
- display: "flex",
14779
- alignItems: "center",
14780
- gap: "8px",
14781
- maxWidth: "230px",
14782
- }, children: event.name }))] }, event.id));
14783
- }) }) })] }, key));
14784
- }) })] }) }));
15091
+ return (jsxRuntime.jsxs(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, footer: footerNav, children: [jsxRuntime.jsxs("div", { style: { padding: "20px 10px" }, children: [jsxRuntime.jsx(FlowProgress, { currentStep: "date", hasUpsellsStep: hasUpsellsStep }), jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
15092
+ const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
15093
+ return (jsxRuntime.jsxs(React.Fragment, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsxRuntime.jsx("div", { style: { height: "1px", backgroundColor: "var(--bw-border-color)", margin: "4px 0" } })), jsxRuntime.jsx(Accordion, { title: label, priceInfo: jsxRuntime.jsx("div", { style: {
15094
+ fontSize: "16px",
15095
+ fontWeight: 500,
15096
+ marginLeft: "auto",
15097
+ padding: "4px 8px",
15098
+ borderRadius: "var(--bw-border-radius-small)",
15099
+ backgroundColor: monthPriceDisplayInfo
15100
+ ? monthPriceDisplayInfo.backgroundColor
15101
+ : "#14532d",
15102
+ color: monthPriceDisplayInfo
15103
+ ? monthPriceDisplayInfo.textColor
15104
+ : "#4ade80",
15105
+ boxShadow: monthPriceDisplayInfo
15106
+ ? "0 2px 4px rgba(0, 0, 0, 0.2)"
15107
+ : undefined,
15108
+ }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
15109
+ const availableSpots = event.maxParticipants - event.participantCount;
15110
+ const isFullyBooked = availableSpots === 0;
15111
+ const startDate = new Date(event.startTime);
15112
+ const isPastEvent = today.toISOString() >= startDate.toISOString();
15113
+ const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
15114
+ const canWaitlist = isFullyBooked &&
15115
+ !isPastEvent &&
15116
+ !!apiBaseUrl &&
15117
+ !!organizationId;
15118
+ return (jsxRuntime.jsxs("div", { style: {
15119
+ position: "relative",
15120
+ border: "1px solid var(--bw-border-color)",
15121
+ backgroundColor: "var(--bw-surface-color)",
15122
+ borderRadius: "var(--bw-border-radius)",
15123
+ padding: "16px 10px",
15124
+ transition: "all 0.2s ease",
15125
+ fontFamily: "var(--bw-font-family)",
15126
+ opacity: isDisabled && !canWaitlist ? 0.3 : 1,
15127
+ filter: isDisabled && !canWaitlist ? "grayscale(40%)" : "none",
15128
+ cursor: isDisabled
15129
+ ? canWaitlist
15130
+ ? "default"
15131
+ : "not-allowed"
15132
+ : "pointer",
15133
+ }, onClick: () => {
15134
+ if (!isDisabled) {
15135
+ handleEventInstanceSelect(event);
15136
+ }
15137
+ }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
15138
+ position: "absolute",
15139
+ inset: 0,
15140
+ backgroundColor: "rgba(15, 23, 42, 0.8)",
15141
+ borderRadius: "var(--bw-border-radius)",
15142
+ display: "flex",
15143
+ alignItems: "center",
15144
+ justifyContent: "center",
15145
+ }, children: jsxRuntime.jsx("div", { style: {
15146
+ width: "32px",
15147
+ height: "32px",
15148
+ color: "var(--bw-highlight-color)",
15149
+ opacity: 0.8,
15150
+ fontSize: "32px",
15151
+ }, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxRuntime.jsxs("div", { style: {
15152
+ display: "flex",
15153
+ justifyContent: "space-between",
15154
+ width: "100%",
15155
+ alignItems: "flex-start",
15156
+ gap: "12px",
15157
+ marginBottom: "4px",
15158
+ }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsxRuntime.jsx("div", { style: {
15159
+ fontSize: "16px",
15160
+ transition: "all 0.2s ease",
15161
+ borderRadius: "var(--bw-border-radius-small)",
15162
+ borderTop: "4px solid var(--bw-border-color)",
15163
+ border: "1px solid var(--bw-border-color)",
15164
+ width: "40px",
15165
+ height: "40px",
15166
+ display: "flex",
15167
+ alignItems: "center",
15168
+ justifyContent: "center",
15169
+ fontWeight: 700,
15170
+ color: "var(--bw-text-color)",
15171
+ backgroundColor: "var(--bw-background-color)",
15172
+ }, children: startDate.getDate() }), jsxRuntime.jsxs("div", { style: {
15173
+ fontSize: "16px",
15174
+ color: "var(--bw-text-color)",
15175
+ display: "flex",
15176
+ flexDirection: "column",
15177
+ alignItems: "flex-start",
15178
+ justifyContent: "flex-start",
15179
+ lineHeight: 1.25,
15180
+ }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
15181
+ formatWeekday(event.endTime, timezone, locale) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsxRuntime.jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsxRuntime.jsx("div", { children: formatWeekday(event.startTime, timezone, locale) ===
15182
+ formatWeekday(event.endTime, timezone, locale) ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxRuntime.jsxs("span", { style: {
15183
+ fontSize: "12px",
15184
+ fontWeight: 400,
15185
+ color: "var(--bw-text-muted)",
15186
+ marginLeft: "6px",
15187
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
15188
+ whiteSpace: "nowrap",
15189
+ padding: "2px 6px",
15190
+ borderRadius: "var(--bw-border-radius-small)",
15191
+ }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsxRuntime.jsx("div", { style: {
15192
+ textAlign: "right",
15193
+ display: "flex",
15194
+ flexDirection: "column",
15195
+ alignItems: "flex-end",
15196
+ }, children: jsxRuntime.jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsxRuntime.jsx("h4", { style: {
15197
+ fontSize: "16px",
15198
+ fontWeight: 600,
15199
+ color: "var(--bw-text-color)",
15200
+ lineHeight: 1.25,
15201
+ margin: "0 0 2px 0",
15202
+ display: "flex",
15203
+ alignItems: "center",
15204
+ gap: "8px",
15205
+ maxWidth: "230px",
15206
+ }, children: event.name })), canWaitlist && (jsxRuntime.jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth, buttonStyles.small, { marginTop: "8px" }), onClick: (e) => {
15207
+ e.stopPropagation();
15208
+ setWaitlistEvent(event);
15209
+ }, children: t("waitlist.joinCta") }))] }, event.id));
15210
+ }) }) })] }, key));
15211
+ }) })] }), waitlistEvent && apiBaseUrl && organizationId && (jsxRuntime.jsx(WaitlistDialog, { apiBaseUrl: apiBaseUrl, organizationId: organizationId, eventInstanceId: waitlistEvent.id, eventName: waitlistEvent.name, onClose: () => setWaitlistEvent(null) }))] }));
14785
15212
  }
14786
15213
 
14787
15214
  function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText, showAllButton, isLoadingEventDetails = false, isLoadingShowAll = false, isLoading = false, count = 3, }) {
@@ -15193,6 +15620,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
15193
15620
  }) })] }));
15194
15621
  }
15195
15622
 
15623
+ function toMinutesForUnit(unit) {
15624
+ switch (unit.toLowerCase()) {
15625
+ case "minute":
15626
+ case "minutes":
15627
+ return 1;
15628
+ case "hour":
15629
+ case "hours":
15630
+ return 60;
15631
+ case "day":
15632
+ case "days":
15633
+ return 24 * 60;
15634
+ default:
15635
+ return 1;
15636
+ }
15637
+ }
15638
+ // --- Timezone helpers ------------------------------------------------------
15639
+ // The booking is evaluated server-side against the organization's local
15640
+ // operating hours. The customer's browser may be in a different timezone, so we
15641
+ // interpret the datetime-local field as a wall-clock time in the ORG timezone
15642
+ // (not the browser's) and convert to a UTC instant for the API.
15643
+ function getTzParts(date, timeZone) {
15644
+ const dtf = new Intl.DateTimeFormat("en-CA", {
15645
+ timeZone,
15646
+ hour12: false,
15647
+ year: "numeric",
15648
+ month: "2-digit",
15649
+ day: "2-digit",
15650
+ hour: "2-digit",
15651
+ minute: "2-digit",
15652
+ second: "2-digit",
15653
+ });
15654
+ const parts = {};
15655
+ for (const part of dtf.formatToParts(date)) {
15656
+ if (part.type !== "literal")
15657
+ parts[part.type] = part.value;
15658
+ }
15659
+ return parts;
15660
+ }
15661
+ function tzOffsetMs(utcMs, timeZone) {
15662
+ const p = getTzParts(new Date(utcMs), timeZone);
15663
+ const hour = p.hour === "24" ? "00" : p.hour;
15664
+ const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
15665
+ return asLocalUtc - utcMs;
15666
+ }
15667
+ /** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
15668
+ function zonedWallTimeToUtc(wallTime, timeZone) {
15669
+ const [datePart, timePart] = wallTime.split("T");
15670
+ if (!datePart || !timePart)
15671
+ return new Date(NaN);
15672
+ const [y, mo, d] = datePart.split("-").map(Number);
15673
+ const [h, mi] = timePart.split(":").map(Number);
15674
+ const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
15675
+ // Single-pass offset correction (good enough outside the ~1h DST fold).
15676
+ const offset = tzOffsetMs(naiveUtc, timeZone);
15677
+ return new Date(naiveUtc - offset);
15678
+ }
15679
+ /** Render a UTC instant as an org-tz datetime-local input value. */
15680
+ function utcToZonedInputValue(date, timeZone) {
15681
+ const p = getTzParts(date, timeZone);
15682
+ const hour = p.hour === "24" ? "00" : p.hour;
15683
+ return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
15684
+ }
15685
+ /** Format a UTC instant for display in the org timezone. */
15686
+ function formatInTimeZone(date, timeZone, locale) {
15687
+ return new Intl.DateTimeFormat(locale, {
15688
+ timeZone,
15689
+ dateStyle: "medium",
15690
+ timeStyle: "short",
15691
+ }).format(date);
15692
+ }
15693
+ function calculateTieredPrice(durationUnits, config) {
15694
+ const tiers = [...(config.tiers ?? [])]
15695
+ .filter((tier) => tier.unitCount > 0 && tier.price >= 0)
15696
+ .sort((a, b) => b.unitCount - a.unitCount);
15697
+ if (tiers.length === 0) {
15698
+ return 0;
15699
+ }
15700
+ let total = 0;
15701
+ let remaining = durationUnits;
15702
+ for (const tier of tiers) {
15703
+ const count = Math.floor(remaining / tier.unitCount);
15704
+ if (count <= 0)
15705
+ continue;
15706
+ total += count * tier.price;
15707
+ remaining -= count * tier.unitCount;
15708
+ }
15709
+ if (remaining > 0) {
15710
+ if (typeof config.perUnitPrice === "number") {
15711
+ total += remaining * config.perUnitPrice;
15712
+ }
15713
+ else {
15714
+ total += tiers[tiers.length - 1]?.price ?? 0;
15715
+ }
15716
+ }
15717
+ return total;
15718
+ }
15719
+ function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
15720
+ const t = useTranslations();
15721
+ const { locale } = useLocale();
15722
+ const freeformConfig = eventType.freeformConfig;
15723
+ const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
15724
+ const isNamedResource = eventType.resourceKind === "named";
15725
+ const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
15726
+ const now = React.useMemo(() => {
15727
+ const next = new Date();
15728
+ next.setMinutes(next.getMinutes() + 30, 0, 0);
15729
+ return next;
15730
+ }, []);
15731
+ const [startInput, setStartInput] = React.useState(utcToZonedInputValue(now, timeZone));
15732
+ const [duration, setDuration] = React.useState(Math.max(1, freeformConfig?.minDuration ?? 1));
15733
+ const [requestedUnits, setRequestedUnits] = React.useState(1);
15734
+ const [customerName, setCustomerName] = React.useState("");
15735
+ const [customerEmail, setCustomerEmail] = React.useState("");
15736
+ const [customerPhone, setCustomerPhone] = React.useState("");
15737
+ const [comment, setComment] = React.useState("");
15738
+ const [acceptTerms, setAcceptTerms] = React.useState(false);
15739
+ const [checkoutStep, setCheckoutStep] = React.useState("details");
15740
+ const [availability, setAvailability] = React.useState(null);
15741
+ const [availabilityError, setAvailabilityError] = React.useState(null);
15742
+ const [isCheckingAvailability, setIsCheckingAvailability] = React.useState(false);
15743
+ React.useEffect(() => {
15744
+ if (!isOpen)
15745
+ return;
15746
+ setCheckoutStep("details");
15747
+ }, [isOpen]);
15748
+ React.useEffect(() => {
15749
+ if (!freeformConfig)
15750
+ return;
15751
+ setDuration(Math.max(1, freeformConfig.minDuration));
15752
+ setRequestedUnits(1);
15753
+ setAvailability(null);
15754
+ setAvailabilityError(null);
15755
+ }, [freeformConfig?.resourceId]);
15756
+ const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
15757
+ const startDate = React.useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
15758
+ const endDate = React.useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
15759
+ React.useEffect(() => {
15760
+ if (!isOpen || !freeformConfig)
15761
+ return;
15762
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
15763
+ return;
15764
+ const timer = setTimeout(async () => {
15765
+ setIsCheckingAvailability(true);
15766
+ setAvailabilityError(null);
15767
+ try {
15768
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
15769
+ method: "POST",
15770
+ headers: createApiHeaders(config, locale),
15771
+ body: JSON.stringify(createRequestBody(config, {
15772
+ eventTypeId: eventType.id,
15773
+ requestedUnits,
15774
+ start: startDate.toISOString(),
15775
+ end: endDate.toISOString(),
15776
+ })),
15777
+ });
15778
+ const data = await response.json();
15779
+ if (!response.ok) {
15780
+ setAvailability(null);
15781
+ setAvailabilityError(data.error || t("error.loadBookingData"));
15782
+ return;
15783
+ }
15784
+ setAvailability({
15785
+ available: Boolean(data.available),
15786
+ freeUnits: Number(data.freeUnits ?? 0),
15787
+ capacityUnits: Number(data.capacityUnits ?? 0),
15788
+ usedUnits: Number(data.usedUnits ?? 0),
15789
+ reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
15790
+ blockUsedUnits: Number(data.blockUsedUnits ?? 0),
15791
+ durationUnits: Number(data.durationUnits ?? 0),
15792
+ roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
15793
+ });
15794
+ }
15795
+ catch (_error) {
15796
+ setAvailability(null);
15797
+ setAvailabilityError(t("error.loadBookingData"));
15798
+ }
15799
+ finally {
15800
+ setIsCheckingAvailability(false);
15801
+ }
15802
+ }, 350);
15803
+ return () => clearTimeout(timer);
15804
+ }, [
15805
+ config,
15806
+ endDate,
15807
+ eventType.id,
15808
+ freeformConfig,
15809
+ isOpen,
15810
+ locale,
15811
+ requestedUnits,
15812
+ startDate,
15813
+ t,
15814
+ ]);
15815
+ const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
15816
+ const basePrice = React.useMemo(() => {
15817
+ if (!freeformConfig)
15818
+ return 0;
15819
+ if (freeformConfig.pricingModel === "perUnit") {
15820
+ return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
15821
+ }
15822
+ return calculateTieredPrice(billedDurationUnits, freeformConfig);
15823
+ }, [billedDurationUnits, freeformConfig]);
15824
+ const totalPrice = Math.max(0, basePrice * requestedUnits);
15825
+ const canContinueToPayment = !!freeformConfig &&
15826
+ customerName.trim().length >= 2 &&
15827
+ customerEmail.trim().length > 3 &&
15828
+ acceptTerms &&
15829
+ !!availability?.available &&
15830
+ !isCheckingAvailability;
15831
+ const freeformSelection = React.useMemo(() => ({
15832
+ eventTypeId: eventType.id,
15833
+ start: startDate.toISOString(),
15834
+ end: endDate.toISOString(),
15835
+ requestedUnits,
15836
+ customerName: customerName.trim(),
15837
+ customerEmail: customerEmail.trim(),
15838
+ customerPhone: customerPhone.trim(),
15839
+ comment: comment.trim(),
15840
+ }), [
15841
+ comment,
15842
+ customerEmail,
15843
+ customerName,
15844
+ customerPhone,
15845
+ endDate,
15846
+ eventType.id,
15847
+ requestedUnits,
15848
+ startDate,
15849
+ ]);
15850
+ const paymentFormData = React.useMemo(() => ({
15851
+ customerName: customerName.trim(),
15852
+ customerEmail: customerEmail.trim(),
15853
+ customerPhone: customerPhone.trim(),
15854
+ participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
15855
+ comment: comment.trim(),
15856
+ }), [comment, customerEmail, customerName, customerPhone]);
15857
+ const eventDetailsForPayment = React.useMemo(() => ({
15858
+ id: eventType.id,
15859
+ name: eventType.name,
15860
+ startTime: startDate.toISOString(),
15861
+ endTime: endDate.toISOString(),
15862
+ price: basePrice,
15863
+ maxParticipants: availability?.capacityUnits ?? requestedUnits,
15864
+ participantCount: 0,
15865
+ availableSpots: availability?.freeUnits ?? requestedUnits,
15866
+ durationDays: 1,
15867
+ durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
15868
+ images: eventType.images ?? [],
15869
+ category: eventType.category,
15870
+ organization: {
15871
+ id: config.organizationId,
15872
+ name: "",
15873
+ },
15874
+ bookingOpen: availability?.available ?? false,
15875
+ }), [
15876
+ availability?.available,
15877
+ availability?.capacityUnits,
15878
+ availability?.freeUnits,
15879
+ availability?.roundedDurationMinutes,
15880
+ basePrice,
15881
+ config.organizationId,
15882
+ duration,
15883
+ endDate,
15884
+ eventType.category,
15885
+ eventType.id,
15886
+ eventType.images,
15887
+ eventType.name,
15888
+ requestedUnits,
15889
+ startDate,
15890
+ unitMinutes,
15891
+ ]);
15892
+ const footer = (jsxRuntime.jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsxRuntime.jsx("button", { type: "button", onClick: () => {
15893
+ if (checkoutStep === "payment") {
15894
+ setCheckoutStep("details");
15895
+ }
15896
+ else {
15897
+ onClose();
15898
+ }
15899
+ }, style: {
15900
+ flex: 1,
15901
+ padding: "12px 16px",
15902
+ border: "1px solid var(--bw-border-color)",
15903
+ borderRadius: "var(--bw-border-radius)",
15904
+ backgroundColor: "var(--bw-surface-color)",
15905
+ color: "var(--bw-text-color)",
15906
+ fontFamily: "var(--bw-font-family)",
15907
+ fontWeight: 600,
15908
+ cursor: "pointer",
15909
+ }, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsxRuntime.jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
15910
+ flex: 1,
15911
+ padding: "12px 16px",
15912
+ border: "none",
15913
+ borderRadius: "var(--bw-border-radius)",
15914
+ backgroundColor: "var(--bw-highlight-color)",
15915
+ color: "var(--bw-button-text-color, #ffffff)",
15916
+ fontFamily: "var(--bw-font-family)",
15917
+ fontWeight: 700,
15918
+ cursor: canContinueToPayment ? "pointer" : "not-allowed",
15919
+ opacity: canContinueToPayment ? 1 : 0.6,
15920
+ }, children: t("button.continueToPayment") }))] }));
15921
+ if (!freeformConfig) {
15922
+ return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: jsxRuntime.jsx("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
15923
+ }
15924
+ return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: jsxRuntime.jsx("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), jsxRuntime.jsx("input", { type: "datetime-local", value: startInput, onChange: (event) => setStartInput(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), jsxRuntime.jsx("input", { type: "number", min: freeformConfig.minDuration, max: freeformConfig.maxDuration, step: freeformConfig.stepDuration, value: duration, onChange: (event) => setDuration(Number(event.target.value)), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), !isNamedResource && (jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), jsxRuntime.jsx("input", { type: "number", min: 1, max: maxUnits, value: requestedUnits, onChange: (event) => setRequestedUnits(Math.min(maxUnits, Math.max(1, Number(event.target.value)))), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }))] }), jsxRuntime.jsxs("div", { style: { marginTop: "12px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: [formatInTimeZone(startDate, timeZone, locale), " - ", formatInTimeZone(endDate, timeZone, locale), eventType.organizationTimezone ? ` (${timeZone})` : ""] }), isCheckingAvailability && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: availability.available ? "var(--bw-success-color)" : "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availability.available
15925
+ ? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
15926
+ : t("booking.freeformNotAvailable") }))] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsx("input", { type: "text", placeholder: t("booking.namePlaceholder"), value: customerName, onChange: (event) => setCustomerName(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("input", { type: "email", placeholder: t("booking.emailPlaceholder"), value: customerEmail, onChange: (event) => setCustomerEmail(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("input", { type: "tel", placeholder: t("booking.phonePlaceholder"), value: customerPhone, onChange: (event) => setCustomerPhone(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("textarea", { placeholder: t("booking.commentPlaceholder"), value: comment, onChange: (event) => setComment(event.target.value), rows: 3, style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)", resize: "vertical" } }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { children: t("booking.duration") }), jsxRuntime.jsx("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [jsxRuntime.jsx("span", { children: t("booking.price") }), jsxRuntime.jsxs("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-color)", fontFamily: "var(--bw-font-family)", fontSize: "20px", fontWeight: 700, marginTop: "10px" }, children: [jsxRuntime.jsx("span", { children: t("summary.totalAmount") }), jsxRuntime.jsx("span", { children: formatCurrency(totalPrice) })] })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(HoldCountdown, {}), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (jsxRuntime.jsx("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode, freeformSelection: freeformSelection })), systemConfig?.paymentProvider === "stripe" && (jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
15927
+ }
15928
+
15196
15929
  const getThemeConfig = (theme = "generic") => {
15197
15930
  switch (theme) {
15198
15931
  case "christmas":
@@ -15675,6 +16408,7 @@ let buffer = [];
15675
16408
  let flushTimer = null;
15676
16409
  let currentApiBaseUrl = "";
15677
16410
  let currentOrganizationId = "";
16411
+ let currentPartnerContractId = null;
15678
16412
  const FLUSH_INTERVAL_MS = 3000;
15679
16413
  function flush() {
15680
16414
  if (buffer.length === 0)
@@ -15716,9 +16450,10 @@ function scheduleFlush() {
15716
16450
  /**
15717
16451
  * Initialise the analytics module. Must be called once before `trackEvent`.
15718
16452
  */
15719
- function initAnalytics(apiBaseUrl, organizationId) {
16453
+ function initAnalytics(apiBaseUrl, organizationId, options) {
15720
16454
  currentApiBaseUrl = apiBaseUrl;
15721
16455
  currentOrganizationId = organizationId;
16456
+ currentPartnerContractId = options?.partnerContractId?.trim() || null;
15722
16457
  if (typeof window !== "undefined") {
15723
16458
  window.addEventListener("pagehide", flush);
15724
16459
  window.addEventListener("visibilitychange", () => {
@@ -15735,9 +16470,12 @@ function trackEvent(event, properties = {}) {
15735
16470
  return;
15736
16471
  if (!currentOrganizationId)
15737
16472
  return;
16473
+ const mergedProperties = currentPartnerContractId
16474
+ ? { ...properties, partnerContractId: currentPartnerContractId }
16475
+ : properties;
15738
16476
  buffer.push({
15739
16477
  event,
15740
- properties,
16478
+ properties: mergedProperties,
15741
16479
  domain: window.location.hostname,
15742
16480
  url: window.location.href,
15743
16481
  referrer: document.referrer,
@@ -15813,7 +16551,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15813
16551
  const [sidebarOpen, setSidebarOpen] = React.useState(false);
15814
16552
  // Booking flow state
15815
16553
  const [eventDetails, setEventDetails] = React.useState(null);
15816
- const [stripePromise, setStripePromise] = React.useState(null);
15817
16554
  // SEPARATED LOADING STATES
15818
16555
  const [isLoading, setIsLoading] = React.useState(true);
15819
16556
  const [isLoadingEventInstances, setIsLoadingEventInstances] = React.useState(false);
@@ -15829,6 +16566,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15829
16566
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
15830
16567
  const [shouldRenderUpsells, setShouldRenderUpsells] = React.useState(false);
15831
16568
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = React.useState(false);
16569
+ const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = React.useState(false);
15832
16570
  // Google Ads config (received from API, set once from the first API response)
15833
16571
  const [googleAdsConfig, setGoogleAdsConfig] = React.useState(null);
15834
16572
  const extractGoogleAdsConfig = (data) => {
@@ -15842,6 +16580,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15842
16580
  // Voucher purchase state
15843
16581
  const [voucherConfig, setVoucherConfig] = React.useState(null);
15844
16582
  const [voucherEventTypes, setVoucherEventTypes] = React.useState([]);
16583
+ const [voucherCategories, setVoucherCategories] = React.useState([]);
15845
16584
  const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = React.useState(false);
15846
16585
  const [isVoucherFormOpen, setIsVoucherFormOpen] = React.useState(false);
15847
16586
  const [voucherPurchaseResult, setVoucherPurchaseResult] = React.useState(null);
@@ -15921,6 +16660,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15921
16660
  setVoucherConfig(mergedConfig);
15922
16661
  extractGoogleAdsConfig(data);
15923
16662
  setVoucherEventTypes(data.eventTypes || []);
16663
+ setVoucherCategories(data.categories || []);
15924
16664
  // Set system config for payment processing
15925
16665
  if (data.paymentProvider) {
15926
16666
  setSystemConfig({
@@ -15948,7 +16688,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15948
16688
  React.useEffect(() => {
15949
16689
  if (!analyticsInitRef.current && config.organizationId) {
15950
16690
  analyticsInitRef.current = true;
15951
- initAnalytics(config.apiBaseUrl, config.organizationId);
16691
+ initAnalytics(config.apiBaseUrl, config.organizationId, {
16692
+ ...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
16693
+ });
15952
16694
  trackEvent("widget_loaded", {
15953
16695
  viewMode,
15954
16696
  eventTypeId: config.eventTypeId,
@@ -15957,7 +16699,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15957
16699
  isStandaloneVoucherMode,
15958
16700
  });
15959
16701
  }
15960
- }, [config.organizationId, config.apiBaseUrl]);
16702
+ }, [config.organizationId, config.apiBaseUrl, config.partnerContractId, viewMode, config.eventTypeId, config.categoryId, config.eventInstanceId, isStandaloneVoucherMode]);
15961
16703
  // Fire widget pageview once when Google Ads config is received from API
15962
16704
  const pageviewFiredRef = React.useRef(false);
15963
16705
  React.useEffect(() => {
@@ -15971,6 +16713,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15971
16713
  const initializeWidget = async () => {
15972
16714
  try {
15973
16715
  setIsLoading(true);
16716
+ if (config.partnerContractId) {
16717
+ const validationResponse = await fetch(getApiUrl(config.apiBaseUrl, `/booking/validate-partner?organizationId=${encodeURIComponent(config.organizationId)}&contractId=${config.partnerContractId}`), {
16718
+ method: "GET",
16719
+ headers: createApiHeaders(config, locale),
16720
+ });
16721
+ if (!validationResponse.ok) {
16722
+ setError("Invalid partner configuration");
16723
+ config.onError?.("Invalid partner configuration");
16724
+ return;
16725
+ }
16726
+ const validationData = (await validationResponse.json());
16727
+ if (!validationData.valid) {
16728
+ setError("Invalid partner configuration");
16729
+ config.onError?.("Invalid partner configuration");
16730
+ return;
16731
+ }
16732
+ }
15974
16733
  // Load voucher config in parallel if voucher mode is requested or there's event selection
15975
16734
  if (isVoucherModeRequested || hasEventSelection) {
15976
16735
  void loadVoucherConfig();
@@ -16106,11 +16865,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16106
16865
  setVoucherPurchaseResult(voucherData.voucherResult);
16107
16866
  setIsSuccess(true);
16108
16867
  setSuccessPaymentId(null);
16868
+ return;
16109
16869
  }
16110
16870
  }
16111
16871
  catch {
16112
- // Ignore lookup errors; webhook eventual consistency may delay data.
16113
- }
16872
+ // Fall back to booking success flow if voucher lookup fails.
16873
+ }
16874
+ // Not a voucher purchase: treat as a booking return and open the
16875
+ // booking confirmation, mirroring the Stripe redirect handler.
16876
+ trackEvent("booking_completed", { paymentIntentId: mollieReturn.molliePaymentId, source: "mollie_redirect" });
16877
+ setVoucherPurchaseResult(null);
16878
+ setSuccessPaymentId(mollieReturn.molliePaymentId);
16879
+ setIsSuccess(true);
16114
16880
  };
16115
16881
  if (!isLoading) {
16116
16882
  void handleMollieVoucherReturn();
@@ -16299,27 +17065,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16299
17065
  widgetPaymentMode: data.widgetPaymentMode ?? "all",
16300
17066
  });
16301
17067
  }
16302
- if (data.stripePublishableKey) {
16303
- const stripeOptions = {
16304
- locale: locale,
16305
- };
16306
- if (data.connectedAccountId) {
16307
- stripeOptions.stripeAccount = data.connectedAccountId;
16308
- }
16309
- if (typeof window !== "undefined" && window.document) {
16310
- try {
16311
- void (window.parent === window || window.parent.location.href);
16312
- if (!stripeOptions.betas) {
16313
- stripeOptions.betas = [];
16314
- }
16315
- }
16316
- catch {
16317
- console.warn("[WIDGET] Detected restricted environment, adjusting Stripe options");
16318
- stripeOptions.betas = [];
16319
- }
16320
- }
16321
- setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16322
- }
16323
17068
  if (!skipInstanceAutoSelectRef.current && data.eventInstances.length === 1) {
16324
17069
  setSelectedEventInstance(data.eventInstances[0]);
16325
17070
  setCurrentStep("booking");
@@ -16359,29 +17104,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16359
17104
  roundPricesEnabled: data.roundPricesEnabled ?? true,
16360
17105
  widgetPaymentMode: data.widgetPaymentMode ?? "all",
16361
17106
  });
16362
- if (!stripePromise && data.stripePublishableKey) {
16363
- const stripeOptions = {
16364
- locale: locale,
16365
- };
16366
- if (data.connectedAccountId) {
16367
- stripeOptions.stripeAccount = data.connectedAccountId;
16368
- }
16369
- if (typeof window !== "undefined" && window.document) {
16370
- try {
16371
- // Check if we have parent access (not in a cross-origin iframe)
16372
- void (window.parent === window || window.parent.location.href);
16373
- stripeOptions.apiVersion = "2025-02-24.acacia";
16374
- if (!stripeOptions.betas) {
16375
- stripeOptions.betas = [];
16376
- }
16377
- }
16378
- catch {
16379
- console.warn("[WIDGET] Detected restricted environment, adjusting Stripe options");
16380
- stripeOptions.betas = [];
16381
- }
16382
- }
16383
- setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16384
- }
16385
17107
  }
16386
17108
  else {
16387
17109
  const errorMessage = data.error || t("error.loadEventDetails");
@@ -16446,6 +17168,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16446
17168
  const handleEventTypeSelect = async (eventType) => {
16447
17169
  trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
16448
17170
  setSelectedEventType(eventType);
17171
+ if (eventType.bookingMode === "freeform") {
17172
+ setCurrentStep("freeform");
17173
+ setShouldRenderFreeformSelection(true);
17174
+ setIsLoadingEventInstances(true);
17175
+ try {
17176
+ await loadEventInstances(eventType.id);
17177
+ }
17178
+ finally {
17179
+ setIsLoadingEventInstances(false);
17180
+ }
17181
+ return;
17182
+ }
16449
17183
  setCurrentStep("eventInstances");
16450
17184
  setShouldRenderInstanceSelection(true);
16451
17185
  setIsLoadingEventInstances(true);
@@ -16536,6 +17270,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16536
17270
  setSuccessPaymentId(result.paymentIntent.id);
16537
17271
  setSidebarOpen(false);
16538
17272
  setShouldRenderBookingForm(false);
17273
+ setShouldRenderFreeformSelection(false);
16539
17274
  setBookingPersistedState(null);
16540
17275
  config.onSuccess?.(result);
16541
17276
  };
@@ -16650,12 +17385,12 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16650
17385
  startTime: cardPreviewItem.startTime,
16651
17386
  endTime: cardPreviewItem.startTime,
16652
17387
  price: cardPreviewItem.price,
16653
- maxParticipants: cardPreviewItem.availableSpots + 1,
16654
- participantCount: 1,
17388
+ maxParticipants: Math.max(cardPreviewItem.availableSpots, 0),
17389
+ participantCount: 0,
16655
17390
  availableSpots: cardPreviewItem.availableSpots,
16656
17391
  durationDays: 1,
16657
17392
  durationPerDay: 1,
16658
- bookingOpen: true,
17393
+ bookingOpen: cardPreviewItem.bookingOpen && cardPreviewItem.availableSpots > 0,
16659
17394
  }
16660
17395
  : null;
16661
17396
  if (eventInstance) {
@@ -16843,7 +17578,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16843
17578
  }
16844
17579
  // Main view based on view mode
16845
17580
  if (viewMode === "next-events" && showingPreview) {
16846
- return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [jsxRuntime.jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading, count: nextEventsSettings.count }), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17581
+ return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [jsxRuntime.jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading, count: nextEventsSettings.count }), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16847
17582
  setIsSuccess(false);
16848
17583
  setCurrentStep("eventTypes");
16849
17584
  setShowingPreview(true);
@@ -16851,6 +17586,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16851
17586
  setShouldRenderInstanceSelection(false);
16852
17587
  setShouldRenderUpsells(false);
16853
17588
  setShouldRenderBookingForm(false);
17589
+ setShouldRenderFreeformSelection(false);
16854
17590
  setSelectedUpsells([]);
16855
17591
  setUpsells([]);
16856
17592
  setBookingPersistedState(null);
@@ -16864,7 +17600,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16864
17600
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16865
17601
  }
16866
17602
  if (viewMode === "specials" && showingPreview) {
16867
- return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [jsxRuntime.jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17603
+ return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [jsxRuntime.jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16868
17604
  setIsSuccess(false);
16869
17605
  setCurrentStep("eventTypes");
16870
17606
  setShowingPreview(true);
@@ -16872,6 +17608,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16872
17608
  setShouldRenderInstanceSelection(false);
16873
17609
  setShouldRenderUpsells(false);
16874
17610
  setShouldRenderBookingForm(false);
17611
+ setShouldRenderFreeformSelection(false);
16875
17612
  setSelectedUpsells([]);
16876
17613
  setUpsells([]);
16877
17614
  setBookingPersistedState(null);
@@ -16884,13 +17621,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16884
17621
  }, isOpen: currentStep === "eventInstances", onClose: () => {
16885
17622
  setShowingPreview(true);
16886
17623
  setCurrentStep("eventTypes");
16887
- }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17624
+ }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16888
17625
  setIsSuccess(false);
16889
17626
  setCurrentStep("eventTypes");
16890
17627
  setShowingPreview(true);
16891
17628
  setSuccessPaymentId(null);
16892
17629
  setShouldRenderInstanceSelection(false);
16893
17630
  setShouldRenderBookingForm(false);
17631
+ setShouldRenderFreeformSelection(false);
16894
17632
  const url = new URL(window.location.href);
16895
17633
  url.searchParams.delete("payment_intent");
16896
17634
  url.searchParams.delete("payment_intent_client_secret");
@@ -16934,13 +17672,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16934
17672
  setCurrentStep("booking");
16935
17673
  setShouldRenderBookingForm(true);
16936
17674
  }
17675
+ else if (selectedEventType?.bookingMode === "freeform") {
17676
+ setCurrentStep("freeform");
17677
+ setShouldRenderFreeformSelection(true);
17678
+ }
16937
17679
  else {
16938
17680
  setCurrentStep("eventInstances");
16939
17681
  setSidebarOpen(true);
16940
17682
  setShouldRenderInstanceSelection(true);
16941
17683
  }
16942
17684
  }, children: [isButtonBusy && jsxRuntime.jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
16943
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17685
+ (isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
17686
+ ? t("button.bookNow")
17687
+ : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17688
+ setCurrentStep("eventTypes");
17689
+ setSidebarOpen(false);
17690
+ setShouldRenderFreeformSelection(false);
17691
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16944
17692
  setIsSuccess(false);
16945
17693
  setCurrentStep("eventTypes");
16946
17694
  setSidebarOpen(false);
@@ -16948,6 +17696,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16948
17696
  setShouldRenderInstanceSelection(false);
16949
17697
  setShouldRenderUpsells(false);
16950
17698
  setShouldRenderBookingForm(false);
17699
+ setShouldRenderFreeformSelection(false);
16951
17700
  setSelectedUpsells([]);
16952
17701
  setUpsells([]);
16953
17702
  const url = new URL(window.location.href);
@@ -16960,7 +17709,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16960
17709
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16961
17710
  }
16962
17711
  // Cards mode (default) - show event type selection with optional voucher card
16963
- const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
17712
+ const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
16964
17713
  display: "inline-block",
16965
17714
  width: "32px",
16966
17715
  height: "32px",
@@ -17001,13 +17750,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17001
17750
  };
17002
17751
  };
17003
17752
  const backHandlers = getBackHandlers();
17004
- return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17753
+ return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17754
+ setCurrentStep("eventTypes");
17755
+ setShouldRenderFreeformSelection(false);
17756
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17005
17757
  setIsSuccess(false);
17006
17758
  setCurrentStep("eventTypes");
17007
17759
  setSuccessPaymentId(null);
17008
17760
  setShouldRenderInstanceSelection(false);
17009
17761
  setShouldRenderUpsells(false);
17010
17762
  setShouldRenderBookingForm(false);
17763
+ setShouldRenderFreeformSelection(false);
17011
17764
  setSelectedUpsells([]);
17012
17765
  setUpsells([]);
17013
17766
  const url = new URL(window.location.href);
@@ -17017,7 +17770,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17017
17770
  url.searchParams.delete("mollie_payment_id");
17018
17771
  url.searchParams.delete("mollie_status");
17019
17772
  window.history.replaceState({}, "", url.toString());
17020
- }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
17773
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
17021
17774
  setIsSuccess(false);
17022
17775
  setVoucherPurchaseResult(null);
17023
17776
  const url = new URL(window.location.href);
@@ -17065,7 +17818,7 @@ function styleInject(css, ref) {
17065
17818
  }
17066
17819
  }
17067
17820
 
17068
- var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.bw-btn{letter-spacing:var(--bw-letter-spacing,normal);text-transform:var(--bw-text-transform,none);transition:all .2s ease!important}.bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.bw-btn:disabled,.bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}button[class*=bw-btn],button[style*=transition]{transition:all .2s ease!important}button[data-variant=primary]:hover:not(:disabled),button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
17821
+ var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.booking-widget-container .bw-btn{letter-spacing:var(--bw-letter-spacing,normal);text-transform:var(--bw-text-transform,none);transition:all .2s ease!important}.booking-widget-container .bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.booking-widget-container .bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.booking-widget-container .bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.booking-widget-container .bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.booking-widget-container .bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.booking-widget-container .bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.booking-widget-container .bw-btn:disabled,.booking-widget-container .bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}.booking-widget-container button[class*=bw-btn],.booking-widget-container button[style*=transition]{transition:all .2s ease!important}.booking-widget-container button[data-variant=primary]:hover:not(:disabled),.booking-widget-container button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}.booking-widget-container button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}.booking-widget-container button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}.booking-widget-container button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.booking-widget-container .bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.booking-widget-container .bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
17069
17822
  styleInject(css_248z);
17070
17823
 
17071
17824
  // Export init function for vanilla JS usage