@bigz-app/booking-widget 1.4.4 → 1.4.5

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 (35) hide show
  1. package/dist/booking-widget.js +17305 -17103
  2. package/dist/booking-widget.js.map +1 -1
  3. package/dist/components/UniversalBookingWidget.d.ts +12 -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.map +1 -1
  11. package/dist/components/booking/StripePaymentForm.d.ts +1 -2
  12. package/dist/components/booking/StripePaymentForm.d.ts.map +1 -1
  13. package/dist/components/events/EventInstanceSelection.d.ts +3 -1
  14. package/dist/components/events/EventInstanceSelection.d.ts.map +1 -1
  15. package/dist/components/events/EventTypeDetailsDialog.d.ts +0 -1
  16. package/dist/components/events/EventTypeDetailsDialog.d.ts.map +1 -1
  17. package/dist/components/events/EventTypeSelection.d.ts +1 -1
  18. package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
  19. package/dist/components/events/WaitlistDialog.d.ts +10 -0
  20. package/dist/components/events/WaitlistDialog.d.ts.map +1 -0
  21. package/dist/components/voucher/VoucherPurchaseForm.d.ts +0 -1
  22. package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
  23. package/dist/components/voucher/useVoucherConfig.d.ts +0 -1
  24. package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
  25. package/dist/i18n/locales/de.d.ts.map +1 -1
  26. package/dist/i18n/locales/en.d.ts.map +1 -1
  27. package/dist/index.cjs +561 -359
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.esm.js +548 -346
  30. package/dist/index.esm.js.map +1 -1
  31. package/dist/utils/analytics.d.ts +3 -1
  32. package/dist/utils/analytics.d.ts.map +1 -1
  33. package/dist/utils.d.ts.map +1 -1
  34. package/dist/validation/booking-schema.d.ts +4 -4
  35. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -4,170 +4,6 @@ import { createRoot } from 'react-dom/client';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import ReactDOM, { createPortal } from 'react-dom';
6
6
 
7
- var V3_URL = 'https://js.stripe.com/v3';
8
- var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
9
- 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';
10
- var findScript = function findScript() {
11
- var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
12
-
13
- for (var i = 0; i < scripts.length; i++) {
14
- var script = scripts[i];
15
-
16
- if (!V3_URL_REGEX.test(script.src)) {
17
- continue;
18
- }
19
-
20
- return script;
21
- }
22
-
23
- return null;
24
- };
25
-
26
- var injectScript = function injectScript(params) {
27
- var queryString = '';
28
- var script = document.createElement('script');
29
- script.src = "".concat(V3_URL).concat(queryString);
30
- var headOrBody = document.head || document.body;
31
-
32
- if (!headOrBody) {
33
- throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
34
- }
35
-
36
- headOrBody.appendChild(script);
37
- return script;
38
- };
39
-
40
- var registerWrapper = function registerWrapper(stripe, startTime) {
41
- if (!stripe || !stripe._registerWrapper) {
42
- return;
43
- }
44
-
45
- stripe._registerWrapper({
46
- name: 'stripe-js',
47
- version: "4.6.0",
48
- startTime: startTime
49
- });
50
- };
51
-
52
- var stripePromise = null;
53
- var onErrorListener = null;
54
- var onLoadListener = null;
55
-
56
- var onError = function onError(reject) {
57
- return function () {
58
- reject(new Error('Failed to load Stripe.js'));
59
- };
60
- };
61
-
62
- var onLoad = function onLoad(resolve, reject) {
63
- return function () {
64
- if (window.Stripe) {
65
- resolve(window.Stripe);
66
- } else {
67
- reject(new Error('Stripe.js not available'));
68
- }
69
- };
70
- };
71
-
72
- var loadScript = function loadScript(params) {
73
- // Ensure that we only attempt to load Stripe.js at most once
74
- if (stripePromise !== null) {
75
- return stripePromise;
76
- }
77
-
78
- stripePromise = new Promise(function (resolve, reject) {
79
- if (typeof window === 'undefined' || typeof document === 'undefined') {
80
- // Resolve to null when imported server side. This makes the module
81
- // safe to import in an isomorphic code base.
82
- resolve(null);
83
- return;
84
- }
85
-
86
- if (window.Stripe) {
87
- resolve(window.Stripe);
88
- return;
89
- }
90
-
91
- try {
92
- var script = findScript();
93
-
94
- if (script && params) ; else if (!script) {
95
- script = injectScript(params);
96
- } else if (script && onLoadListener !== null && onErrorListener !== null) {
97
- var _script$parentNode;
98
-
99
- // remove event listeners
100
- script.removeEventListener('load', onLoadListener);
101
- script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
102
- // reload script to trigger 'load' event
103
-
104
- (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
105
- script = injectScript(params);
106
- }
107
-
108
- onLoadListener = onLoad(resolve, reject);
109
- onErrorListener = onError(reject);
110
- script.addEventListener('load', onLoadListener);
111
- script.addEventListener('error', onErrorListener);
112
- } catch (error) {
113
- reject(error);
114
- return;
115
- }
116
- }); // Resets stripePromise on error
117
-
118
- return stripePromise["catch"](function (error) {
119
- stripePromise = null;
120
- return Promise.reject(error);
121
- });
122
- };
123
- var initStripe = function initStripe(maybeStripe, args, startTime) {
124
- if (maybeStripe === null) {
125
- return null;
126
- }
127
-
128
- var stripe = maybeStripe.apply(undefined, args);
129
- registerWrapper(stripe, startTime);
130
- return stripe;
131
- }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
132
-
133
- var stripePromise$1;
134
- var loadCalled = false;
135
-
136
- var getStripePromise = function getStripePromise() {
137
- if (stripePromise$1) {
138
- return stripePromise$1;
139
- }
140
-
141
- stripePromise$1 = loadScript(null)["catch"](function (error) {
142
- // clear cache on error
143
- stripePromise$1 = null;
144
- return Promise.reject(error);
145
- });
146
- return stripePromise$1;
147
- }; // Execute our own script injection after a tick to give users time to do their
148
- // own script injection.
149
-
150
-
151
- Promise.resolve().then(function () {
152
- return getStripePromise();
153
- })["catch"](function (error) {
154
- if (!loadCalled) {
155
- console.warn(error);
156
- }
157
- });
158
- var loadStripe = function loadStripe() {
159
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
160
- args[_key] = arguments[_key];
161
- }
162
-
163
- loadCalled = true;
164
- var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
165
-
166
- return getStripePromise().then(function (maybeStripe) {
167
- return initStripe(maybeStripe, args, startTime);
168
- });
169
- };
170
-
171
7
  const de$1 = {
172
8
  // Common
173
9
  "common.back": "← Zurück",
@@ -247,6 +83,24 @@ const de$1 = {
247
83
  "instances.bestPrice": "bester Preis !!!",
248
84
  "instances.goodPrice": "günstiger Preis",
249
85
  "instances.oclock": "Uhr",
86
+ // Warteliste
87
+ "waitlist.joinCta": "Auf die Warteliste",
88
+ "waitlist.title": "Auf die Warteliste setzen",
89
+ "waitlist.subtitle": "{{name}} ist ausgebucht. Hinterlassen Sie Ihre Daten und wir melden uns, falls ein Platz frei wird.",
90
+ "waitlist.name": "Name",
91
+ "waitlist.email": "E-Mail",
92
+ "waitlist.phone": "Telefon (optional)",
93
+ "waitlist.requiredFields": "Bitte geben Sie Ihren Namen und Ihre E-Mail-Adresse ein.",
94
+ "waitlist.join": "Eintragen",
95
+ "waitlist.submitting": "Wird eingetragen...",
96
+ "waitlist.cancel": "Abbrechen",
97
+ "waitlist.submitError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.",
98
+ "waitlist.successTitle": "Sie sind auf der Liste!",
99
+ "waitlist.successMessage": "Sie sind Nummer {{position}} auf der Warteliste. Wir melden uns, sobald ein Platz frei wird.",
100
+ "waitlist.done": "Fertig",
101
+ // Reservierungs-Countdown
102
+ "hold.reservedFor": "Platz reserviert für {{time}}",
103
+ "hold.expired": "Ihre Reservierung ist abgelaufen",
250
104
  // Next events preview
251
105
  "nextEvents.title": "Nächste verfügbare Termine",
252
106
  "nextEvents.subtitle": "Wähle einen Termin aus oder zeige alle verfügbaren Termine an",
@@ -537,6 +391,24 @@ const en = {
537
391
  "instances.bestPrice": "best price !!!",
538
392
  "instances.goodPrice": "good price",
539
393
  "instances.oclock": "",
394
+ // Waitlist
395
+ "waitlist.joinCta": "Join waitlist",
396
+ "waitlist.title": "Join the waitlist",
397
+ "waitlist.subtitle": "{{name}} is fully booked. Leave your details and we'll contact you if a spot opens up.",
398
+ "waitlist.name": "Name",
399
+ "waitlist.email": "Email",
400
+ "waitlist.phone": "Phone (optional)",
401
+ "waitlist.requiredFields": "Please enter your name and email.",
402
+ "waitlist.join": "Join waitlist",
403
+ "waitlist.submitting": "Joining...",
404
+ "waitlist.cancel": "Cancel",
405
+ "waitlist.submitError": "Something went wrong. Please try again.",
406
+ "waitlist.successTitle": "You're on the list!",
407
+ "waitlist.successMessage": "You are number {{position}} on the waitlist. We'll be in touch if a spot becomes available.",
408
+ "waitlist.done": "Done",
409
+ // Slot hold countdown
410
+ "hold.reservedFor": "Slot reserved for {{time}}",
411
+ "hold.expired": "Your reservation has expired",
540
412
  // Next events preview
541
413
  "nextEvents.title": "Next available dates",
542
414
  "nextEvents.subtitle": "Select a date or view all available dates",
@@ -2242,6 +2114,7 @@ const inferConfigFromUrl = (baseConfig) => {
2242
2114
  const categoryId = urlParams.get("categoryId") || urlParams.get("category");
2243
2115
  const eventTypeId = urlParams.get("eventTypeId") || urlParams.get("type");
2244
2116
  const eventTypeIds = urlParams.get("eventTypeIds") || urlParams.get("types");
2117
+ const partnerContractId = urlParams.get("partnerContractId") || urlParams.get("contractId");
2245
2118
  const voucherValue = urlParams.get("voucherValue");
2246
2119
  const voucherCategory = urlParams.get("voucherCategory");
2247
2120
  const voucherEventType = urlParams.get("voucherEventType");
@@ -2254,11 +2127,14 @@ const inferConfigFromUrl = (baseConfig) => {
2254
2127
  : ["0", "false", "no", "off"].includes(voucherCardIntegration.toLowerCase())
2255
2128
  ? false
2256
2129
  : undefined;
2257
- const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, ...rest }) => rest;
2130
+ const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, partnerContractId: _pc, ...rest }) => rest;
2258
2131
  if (eventInstanceId && !Number.isNaN(Number(eventInstanceId))) {
2259
2132
  return {
2260
2133
  ...omitSelectionKeys(baseConfig),
2261
2134
  eventInstanceId: Number(eventInstanceId),
2135
+ ...(partnerContractId
2136
+ ? { partnerContractId }
2137
+ : {}),
2262
2138
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2263
2139
  };
2264
2140
  }
@@ -2266,6 +2142,9 @@ const inferConfigFromUrl = (baseConfig) => {
2266
2142
  return {
2267
2143
  ...omitSelectionKeys(baseConfig),
2268
2144
  categoryId: Number(categoryId),
2145
+ ...(partnerContractId
2146
+ ? { partnerContractId }
2147
+ : {}),
2269
2148
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2270
2149
  };
2271
2150
  }
@@ -2273,6 +2152,9 @@ const inferConfigFromUrl = (baseConfig) => {
2273
2152
  return {
2274
2153
  ...omitSelectionKeys(baseConfig),
2275
2154
  eventTypeId: Number(eventTypeId),
2155
+ ...(partnerContractId
2156
+ ? { partnerContractId }
2157
+ : {}),
2276
2158
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2277
2159
  };
2278
2160
  }
@@ -2285,6 +2167,9 @@ const inferConfigFromUrl = (baseConfig) => {
2285
2167
  return {
2286
2168
  ...omitSelectionKeys(baseConfig),
2287
2169
  eventTypeIds: typeIds,
2170
+ ...(partnerContractId
2171
+ ? { partnerContractId }
2172
+ : {}),
2288
2173
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2289
2174
  };
2290
2175
  }
@@ -2301,6 +2186,9 @@ const inferConfigFromUrl = (baseConfig) => {
2301
2186
  if (parsedVoucherValues.length || parsedVoucherCategories.length || parsedVoucherEventTypes.length) {
2302
2187
  return {
2303
2188
  ...baseConfig,
2189
+ ...(partnerContractId
2190
+ ? { partnerContractId }
2191
+ : {}),
2304
2192
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2305
2193
  ...(parsedVoucherValues.length > 0
2306
2194
  ? { voucherValue: parsedVoucherValues.length === 1 ? parsedVoucherValues[0] : parsedVoucherValues }
@@ -2318,6 +2206,9 @@ const inferConfigFromUrl = (baseConfig) => {
2318
2206
  }
2319
2207
  return {
2320
2208
  ...baseConfig,
2209
+ ...(partnerContractId
2210
+ ? { partnerContractId }
2211
+ : {}),
2321
2212
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2322
2213
  };
2323
2214
  };
@@ -6330,6 +6221,7 @@ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, gif
6330
6221
  customerPhone: formData.customerPhone?.trim(),
6331
6222
  comment: formData.comment?.trim(),
6332
6223
  paymentMethod: "gift_card",
6224
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6333
6225
  ...(upsellSelections.length > 0 && { upsellSelections }),
6334
6226
  };
6335
6227
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
@@ -6606,6 +6498,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6606
6498
  customerEmail: formData.customerEmail?.trim(),
6607
6499
  customerPhone: formData.customerPhone?.trim(),
6608
6500
  comment: formData.comment?.trim(),
6501
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6609
6502
  ...(cardToken && { cardToken }),
6610
6503
  ...(selectedMethod && !cardToken && { method: selectedMethod }),
6611
6504
  ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
@@ -6832,6 +6725,170 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6832
6725
  }, children: isLoading ? (jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", buttonLabel] })) : (buttonLabel) }))] }));
6833
6726
  }
6834
6727
 
6728
+ var V3_URL = 'https://js.stripe.com/v3';
6729
+ var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
6730
+ 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';
6731
+ var findScript = function findScript() {
6732
+ var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
6733
+
6734
+ for (var i = 0; i < scripts.length; i++) {
6735
+ var script = scripts[i];
6736
+
6737
+ if (!V3_URL_REGEX.test(script.src)) {
6738
+ continue;
6739
+ }
6740
+
6741
+ return script;
6742
+ }
6743
+
6744
+ return null;
6745
+ };
6746
+
6747
+ var injectScript = function injectScript(params) {
6748
+ var queryString = '';
6749
+ var script = document.createElement('script');
6750
+ script.src = "".concat(V3_URL).concat(queryString);
6751
+ var headOrBody = document.head || document.body;
6752
+
6753
+ if (!headOrBody) {
6754
+ throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
6755
+ }
6756
+
6757
+ headOrBody.appendChild(script);
6758
+ return script;
6759
+ };
6760
+
6761
+ var registerWrapper = function registerWrapper(stripe, startTime) {
6762
+ if (!stripe || !stripe._registerWrapper) {
6763
+ return;
6764
+ }
6765
+
6766
+ stripe._registerWrapper({
6767
+ name: 'stripe-js',
6768
+ version: "4.6.0",
6769
+ startTime: startTime
6770
+ });
6771
+ };
6772
+
6773
+ var stripePromise = null;
6774
+ var onErrorListener = null;
6775
+ var onLoadListener = null;
6776
+
6777
+ var onError = function onError(reject) {
6778
+ return function () {
6779
+ reject(new Error('Failed to load Stripe.js'));
6780
+ };
6781
+ };
6782
+
6783
+ var onLoad = function onLoad(resolve, reject) {
6784
+ return function () {
6785
+ if (window.Stripe) {
6786
+ resolve(window.Stripe);
6787
+ } else {
6788
+ reject(new Error('Stripe.js not available'));
6789
+ }
6790
+ };
6791
+ };
6792
+
6793
+ var loadScript = function loadScript(params) {
6794
+ // Ensure that we only attempt to load Stripe.js at most once
6795
+ if (stripePromise !== null) {
6796
+ return stripePromise;
6797
+ }
6798
+
6799
+ stripePromise = new Promise(function (resolve, reject) {
6800
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
6801
+ // Resolve to null when imported server side. This makes the module
6802
+ // safe to import in an isomorphic code base.
6803
+ resolve(null);
6804
+ return;
6805
+ }
6806
+
6807
+ if (window.Stripe) {
6808
+ resolve(window.Stripe);
6809
+ return;
6810
+ }
6811
+
6812
+ try {
6813
+ var script = findScript();
6814
+
6815
+ if (script && params) ; else if (!script) {
6816
+ script = injectScript(params);
6817
+ } else if (script && onLoadListener !== null && onErrorListener !== null) {
6818
+ var _script$parentNode;
6819
+
6820
+ // remove event listeners
6821
+ script.removeEventListener('load', onLoadListener);
6822
+ script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
6823
+ // reload script to trigger 'load' event
6824
+
6825
+ (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
6826
+ script = injectScript(params);
6827
+ }
6828
+
6829
+ onLoadListener = onLoad(resolve, reject);
6830
+ onErrorListener = onError(reject);
6831
+ script.addEventListener('load', onLoadListener);
6832
+ script.addEventListener('error', onErrorListener);
6833
+ } catch (error) {
6834
+ reject(error);
6835
+ return;
6836
+ }
6837
+ }); // Resets stripePromise on error
6838
+
6839
+ return stripePromise["catch"](function (error) {
6840
+ stripePromise = null;
6841
+ return Promise.reject(error);
6842
+ });
6843
+ };
6844
+ var initStripe = function initStripe(maybeStripe, args, startTime) {
6845
+ if (maybeStripe === null) {
6846
+ return null;
6847
+ }
6848
+
6849
+ var stripe = maybeStripe.apply(undefined, args);
6850
+ registerWrapper(stripe, startTime);
6851
+ return stripe;
6852
+ }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
6853
+
6854
+ var stripePromise$1;
6855
+ var loadCalled = false;
6856
+
6857
+ var getStripePromise = function getStripePromise() {
6858
+ if (stripePromise$1) {
6859
+ return stripePromise$1;
6860
+ }
6861
+
6862
+ stripePromise$1 = loadScript(null)["catch"](function (error) {
6863
+ // clear cache on error
6864
+ stripePromise$1 = null;
6865
+ return Promise.reject(error);
6866
+ });
6867
+ return stripePromise$1;
6868
+ }; // Execute our own script injection after a tick to give users time to do their
6869
+ // own script injection.
6870
+
6871
+
6872
+ Promise.resolve().then(function () {
6873
+ return getStripePromise();
6874
+ })["catch"](function (error) {
6875
+ if (!loadCalled) {
6876
+ console.warn(error);
6877
+ }
6878
+ });
6879
+ var loadStripe = function loadStripe() {
6880
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
6881
+ args[_key] = arguments[_key];
6882
+ }
6883
+
6884
+ loadCalled = true;
6885
+ var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
6886
+
6887
+ return getStripePromise().then(function (maybeStripe) {
6888
+ return initStripe(maybeStripe, args, startTime);
6889
+ });
6890
+ };
6891
+
6835
6892
  // Inner component that uses the Stripe hooks
6836
6893
  function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
6837
6894
  const t = useTranslations();
@@ -6860,13 +6917,25 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
6860
6917
  },
6861
6918
  }, submitContent: jsx(Fragment, { children: submitLabel }), loadingContent: jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
6862
6919
  }
6863
- function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
6920
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
6864
6921
  const t = useTranslations();
6865
6922
  const { locale } = useLocale();
6866
6923
  const [clientSecret, setClientSecret] = useState(null);
6867
6924
  const [paymentIntentId, setPaymentIntentId] = useState(null);
6868
6925
  const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = useState(false);
6869
6926
  const [paymentError, setPaymentError] = useState(null);
6927
+ const stripePromise = useMemo(() => {
6928
+ if (!systemConfig?.stripePublishableKey) {
6929
+ return null;
6930
+ }
6931
+ const stripeOptions = {
6932
+ locale: locale,
6933
+ };
6934
+ if (systemConfig.connectedAccountId) {
6935
+ stripeOptions.stripeAccount = systemConfig.connectedAccountId;
6936
+ }
6937
+ return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
6938
+ }, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
6870
6939
  const storageKey = typeof window !== "undefined"
6871
6940
  ? `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
6872
6941
  : "";
@@ -6945,6 +7014,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6945
7014
  customerEmail: formData.customerEmail?.trim(),
6946
7015
  customerPhone: formData.customerPhone?.trim(),
6947
7016
  comment: formData.comment?.trim(),
7017
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6948
7018
  ...(paymentIntentId && { paymentIntentId }),
6949
7019
  ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6950
7020
  };
@@ -7014,7 +7084,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7014
7084
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
7015
7085
  return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
7016
7086
  }
7017
- if (isCreatingPaymentIntent || !clientSecret) {
7087
+ if (isCreatingPaymentIntent || !clientSecret || !stripePromise) {
7018
7088
  return (jsxs("div", { style: {
7019
7089
  display: "flex",
7020
7090
  alignItems: "center",
@@ -7052,6 +7122,49 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7052
7122
  }, onError: onError }) }));
7053
7123
  }
7054
7124
 
7125
+ const HOLD_DURATION_SECONDS = 15 * 60;
7126
+ /**
7127
+ * Shows the remaining time on the 15-minute slot hold that the server places on
7128
+ * the pending order while the customer completes checkout. The timer is started
7129
+ * client-side when the payment step opens — matching the moment the pending
7130
+ * order (and its `holdExpiresAt`) is created server-side.
7131
+ */
7132
+ function HoldCountdown({ onExpire }) {
7133
+ const t = useTranslations();
7134
+ const [remaining, setRemaining] = useState(HOLD_DURATION_SECONDS);
7135
+ useEffect(() => {
7136
+ const startedAt = Date.now();
7137
+ const interval = setInterval(() => {
7138
+ const elapsed = Math.floor((Date.now() - startedAt) / 1000);
7139
+ const left = Math.max(0, HOLD_DURATION_SECONDS - elapsed);
7140
+ setRemaining(left);
7141
+ if (left <= 0) {
7142
+ clearInterval(interval);
7143
+ onExpire?.();
7144
+ }
7145
+ }, 1000);
7146
+ return () => clearInterval(interval);
7147
+ }, [onExpire]);
7148
+ const minutes = Math.floor(remaining / 60);
7149
+ const seconds = remaining % 60;
7150
+ const formatted = `${minutes}:${String(seconds).padStart(2, "0")}`;
7151
+ const isLow = remaining <= 60;
7152
+ return (jsxs("div", { style: {
7153
+ display: "flex",
7154
+ alignItems: "center",
7155
+ gap: "8px",
7156
+ padding: "10px 14px",
7157
+ borderRadius: "var(--bw-border-radius)",
7158
+ border: `1px solid ${isLow ? "var(--bw-error-color)" : "var(--bw-border-color)"}`,
7159
+ backgroundColor: "var(--bw-surface-color)",
7160
+ color: isLow ? "var(--bw-error-color)" : "var(--bw-text-color)",
7161
+ fontFamily: "var(--bw-font-family)",
7162
+ fontSize: "14px",
7163
+ }, children: [jsx(IconClock, {}), jsx("span", { children: remaining > 0
7164
+ ? t("hold.reservedFor", { time: formatted })
7165
+ : t("hold.expired") })] }));
7166
+ }
7167
+
7055
7168
  const FLAG_EMOJIS = {
7056
7169
  de: "🇩🇪",
7057
7170
  en: "🇬🇧",
@@ -11463,6 +11576,11 @@ const buttonStyles = {
11463
11576
  color: "var(--bw-text-muted)",
11464
11577
  border: "1px solid var(--bw-border-color)",
11465
11578
  },
11579
+ // Size variants
11580
+ small: {
11581
+ padding: "8px 12px",
11582
+ fontSize: "13px",
11583
+ },
11466
11584
  // Full width modifier
11467
11585
  fullWidth: {
11468
11586
  width: "100%",
@@ -11535,6 +11653,12 @@ const sectionStyles = {
11535
11653
  // TEXT
11536
11654
  // ============================================
11537
11655
  const textStyles = {
11656
+ body: {
11657
+ fontSize: "14px",
11658
+ color: "var(--bw-text-color)",
11659
+ fontFamily: "var(--bw-font-family)",
11660
+ lineHeight: 1.5,
11661
+ },
11538
11662
  muted: {
11539
11663
  fontSize: "14px",
11540
11664
  color: "var(--bw-text-muted)",
@@ -11604,7 +11728,7 @@ const sectionHeaderStyles$1 = sectionStyles.header;
11604
11728
  const labelStyles$1 = formStyles.label;
11605
11729
  const inputStyles$1 = formStyles.input;
11606
11730
  const errorTextStyles$1 = formStyles.error;
11607
- function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
11731
+ function BookingForm({ config, eventDetails, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
11608
11732
  const t$1 = useTranslations();
11609
11733
  const { locale } = useLocale();
11610
11734
  const timezone = useTimezone();
@@ -11804,7 +11928,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11804
11928
  newTotal: appliedDiscountCode.newTotal,
11805
11929
  }
11806
11930
  : null;
11807
- const hasPaymentProvider = Boolean(stripePromise || systemConfig?.paymentProvider === "mollie");
11931
+ const hasPaymentProvider = systemConfig?.paymentProvider === "stripe" || systemConfig?.paymentProvider === "mollie";
11808
11932
  const handleVoucherValidated = useCallback((voucher, _error) => {
11809
11933
  if (voucher) {
11810
11934
  setAppliedVouchers((prev) => [...prev, voucher]);
@@ -12070,7 +12194,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
12070
12194
  ...inputStyles$1,
12071
12195
  resize: "vertical",
12072
12196
  minHeight: "80px",
12073
- } })] })] })] })), checkoutStep === "payment" && (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: "16px" }, children: t$1("summary.title") }), jsxs("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("booking.price") }), jsxs("div", { style: {
12197
+ } })] })] })] })), checkoutStep === "payment" && (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [jsx(HoldCountdown, {}), jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: "16px" }, children: t$1("summary.title") }), jsxs("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("booking.price") }), jsxs("div", { style: {
12074
12198
  color: "var(--bw-text-color)",
12075
12199
  fontWeight: 500,
12076
12200
  fontFamily: "var(--bw-font-family)",
@@ -12174,7 +12298,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
12174
12298
  fontFamily: "var(--bw-font-family)",
12175
12299
  marginTop: "8px",
12176
12300
  textAlign: "right",
12177
- }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.depositNotConfigured") }) })), !hasPaymentProvider && !depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.paymentUnavailable") }) })), hasPaymentProvider && !depositsOnlyBlocked && (systemConfig?.paymentProvider === "mollie" ? (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), 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 })] })) : (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), 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() })] })))] }))] }) }));
12301
+ }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.depositNotConfigured") }) })), !hasPaymentProvider && !depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.paymentUnavailable") }) })), hasPaymentProvider && !depositsOnlyBlocked && (systemConfig?.paymentProvider === "mollie" ? (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), 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 })] })) : (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), 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() })] })))] }))] }) }));
12178
12302
  }
12179
12303
 
12180
12304
  /**
@@ -13283,6 +13407,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13283
13407
  const [stripeAppearance, setStripeAppearance] = useState(null);
13284
13408
  // Initialize Stripe
13285
13409
  useEffect(() => {
13410
+ if (!isOpen)
13411
+ return;
13286
13412
  if (paymentProvider !== "stripe")
13287
13413
  return;
13288
13414
  if (systemConfig?.stripePublishableKey && !stripePromise) {
@@ -13294,7 +13420,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13294
13420
  }
13295
13421
  setStripePromise(loadStripe(systemConfig.stripePublishableKey, stripeOptions));
13296
13422
  }
13297
- }, [paymentProvider, systemConfig, locale, stripePromise]);
13423
+ }, [isOpen, paymentProvider, systemConfig, locale, stripePromise]);
13298
13424
  // Initialize Stripe appearance
13299
13425
  useEffect(() => {
13300
13426
  const container = document.querySelector(".booking-widget-container");
@@ -14258,8 +14384,17 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14258
14384
  if (!item)
14259
14385
  return jsx("div", { style: { height: "34px" } }, i);
14260
14386
  const hasDiscount = item.basePrice > 0 && item.price < item.basePrice;
14261
- return (jsxs("div", { onClick: (e) => { e.stopPropagation(); onInstancePreview?.(item.id, eventType.id); }, onMouseEnter: (e) => { if (onInstancePreview)
14262
- e.currentTarget.style.backgroundColor = "var(--bw-border-color)"; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14387
+ const isRowBookable = item.bookingOpen && item.availableSpots > 0;
14388
+ return (jsxs("div", { onClick: (e) => {
14389
+ e.stopPropagation();
14390
+ if (isRowBookable) {
14391
+ onInstancePreview?.(item.id, eventType.id);
14392
+ }
14393
+ }, onMouseEnter: (e) => {
14394
+ if (onInstancePreview && isRowBookable) {
14395
+ e.currentTarget.style.backgroundColor = "var(--bw-highlight-subtle)";
14396
+ }
14397
+ }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14263
14398
  height: "34px",
14264
14399
  display: "grid",
14265
14400
  gridTemplateColumns: "auto 1fr auto 20px",
@@ -14268,12 +14403,13 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14268
14403
  width: "100%",
14269
14404
  fontSize: "13px",
14270
14405
  color: "var(--bw-text-muted)",
14271
- cursor: onInstancePreview ? "pointer" : "default",
14406
+ cursor: onInstancePreview && isRowBookable ? "pointer" : "default",
14272
14407
  borderRadius: "4px",
14273
14408
  padding: "0 2px",
14274
14409
  transition: "background 0.15s",
14275
14410
  boxSizing: "border-box",
14276
- }, children: [jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.specialDescription ? (jsx(InfoBadge, { text: item.specialDescription })) : (jsx("span", {}))] }, item.id));
14411
+ opacity: isRowBookable ? 1 : 0.55,
14412
+ }, children: [jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.isSpecial && item.specialDescription ? (jsx(InfoBadge, { text: item.specialDescription })) : (jsx("span", {}))] }, item.id));
14277
14413
  }) }), hasMoreDates && (jsx("button", { type: "button", onClick: (e) => {
14278
14414
  e.stopPropagation();
14279
14415
  onEventTypeSelect(eventType);
@@ -14362,6 +14498,74 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14362
14498
  }) }) })), jsx(PaymentLogosStrip, {}), jsx(EventTypeDetailsDialog, { isOpen: detailsDialogOpen, onClose: handleCloseDetails, eventType: selectedEventTypeForDetails, onEventTypeSelect: onEventTypeSelect })] }));
14363
14499
  }
14364
14500
 
14501
+ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName, onClose, }) {
14502
+ const t = useTranslations();
14503
+ const [name, setName] = useState("");
14504
+ const [email, setEmail] = useState("");
14505
+ const [phone, setPhone] = useState("");
14506
+ const [isSubmitting, setIsSubmitting] = useState(false);
14507
+ const [error, setError] = useState(null);
14508
+ const [position, setPosition] = useState(null);
14509
+ const handleSubmit = async () => {
14510
+ setError(null);
14511
+ if (!name.trim() || !email.trim()) {
14512
+ setError(t("waitlist.requiredFields"));
14513
+ return;
14514
+ }
14515
+ setIsSubmitting(true);
14516
+ try {
14517
+ const response = await fetch(getApiUrl(apiBaseUrl, "/booking/waitlist"), {
14518
+ method: "POST",
14519
+ headers: { "Content-Type": "application/json" },
14520
+ body: JSON.stringify({
14521
+ organizationId,
14522
+ eventInstanceId,
14523
+ name: name.trim(),
14524
+ email: email.trim(),
14525
+ phone: phone.trim() || undefined,
14526
+ }),
14527
+ });
14528
+ if (!response.ok) {
14529
+ throw new Error("request_failed");
14530
+ }
14531
+ const data = (await response.json());
14532
+ setPosition(data.position ?? null);
14533
+ }
14534
+ catch {
14535
+ setError(t("waitlist.submitError"));
14536
+ }
14537
+ finally {
14538
+ setIsSubmitting(false);
14539
+ }
14540
+ };
14541
+ return (jsx("div", { style: {
14542
+ position: "fixed",
14543
+ inset: 0,
14544
+ zIndex: 1000,
14545
+ display: "flex",
14546
+ alignItems: "center",
14547
+ justifyContent: "center",
14548
+ backgroundColor: "rgba(15, 23, 42, 0.6)",
14549
+ padding: "16px",
14550
+ fontFamily: "var(--bw-font-family)",
14551
+ }, onClick: onClose, children: jsx("div", { style: {
14552
+ width: "100%",
14553
+ maxWidth: "420px",
14554
+ backgroundColor: "var(--bw-surface-color)",
14555
+ border: "1px solid var(--bw-border-color)",
14556
+ borderRadius: "var(--bw-border-radius)",
14557
+ padding: "24px",
14558
+ }, onClick: (e) => e.stopPropagation(), children: position !== null ? (jsxs("div", { style: { textAlign: "center" }, children: [jsx("h3", { style: {
14559
+ margin: "0 0 8px",
14560
+ color: "var(--bw-highlight-color)",
14561
+ fontSize: "18px",
14562
+ }, children: t("waitlist.successTitle") }), jsx("p", { style: mergeStyles(textStyles.body, { marginBottom: "20px" }), children: t("waitlist.successMessage", { position }) }), jsx("button", { type: "button", style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), onClick: onClose, children: t("waitlist.done") })] })) : (jsxs(Fragment, { children: [jsx("h3", { style: {
14563
+ margin: "0 0 4px",
14564
+ color: "var(--bw-highlight-color)",
14565
+ fontSize: "18px",
14566
+ }, children: t("waitlist.title") }), jsx("p", { style: mergeStyles(textStyles.muted, { marginBottom: "16px" }), children: t("waitlist.subtitle", { name: eventName }) }), jsxs("div", { style: { marginBottom: "12px" }, children: [jsx("label", { style: formStyles.label, htmlFor: "wl-name", children: t("waitlist.name") }), jsx("input", { id: "wl-name", style: formStyles.input, value: name, onChange: (e) => setName(e.target.value) })] }), jsxs("div", { style: { marginBottom: "12px" }, children: [jsx("label", { style: formStyles.label, htmlFor: "wl-email", children: t("waitlist.email") }), jsx("input", { id: "wl-email", type: "email", style: formStyles.input, value: email, onChange: (e) => setEmail(e.target.value) })] }), jsxs("div", { style: { marginBottom: "12px" }, children: [jsx("label", { style: formStyles.label, htmlFor: "wl-phone", children: t("waitlist.phone") }), jsx("input", { id: "wl-phone", type: "tel", style: formStyles.input, value: phone, onChange: (e) => setPhone(e.target.value) })] }), error && jsx("p", { style: formStyles.error, children: error }), jsxs("div", { style: { display: "flex", gap: "8px", marginTop: "16px" }, children: [jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), onClick: onClose, disabled: isSubmitting, children: t("waitlist.cancel") }), jsx("button", { type: "button", style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), onClick: handleSubmit, disabled: isSubmitting, children: isSubmitting ? t("waitlist.submitting") : t("waitlist.join") })] })] })) }) }));
14567
+ }
14568
+
14365
14569
  const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
14366
14570
  const availabilityRatio = availableSpots / maxParticipants;
14367
14571
  if (availabilityRatio >= 0.6)
@@ -14486,12 +14690,13 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
14486
14690
  boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
14487
14691
  }, children: formatCurrency(price) }));
14488
14692
  };
14489
- function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, }) {
14693
+ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
14490
14694
  const t = useTranslations();
14491
14695
  const { locale } = useLocale();
14492
14696
  const timezone = useTimezone();
14493
14697
  const [selectedEventInstanceId, setSelectedEventInstanceId] = useState(null);
14494
14698
  const [openGroups, setOpenGroups] = useState(new Set());
14699
+ const [waitlistEvent, setWaitlistEvent] = useState(null);
14495
14700
  const getMonthPriceDisplayInfo = (minPrice) => {
14496
14701
  return getPriceDisplayInfo(minPrice, yearPrices, t);
14497
14702
  };
@@ -14652,116 +14857,127 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14652
14857
  fontFamily: "var(--bw-font-family)",
14653
14858
  }, children: t("instances.noAvailable") }), jsx("p", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("instances.noAvailableMessage") })] }) }) }));
14654
14859
  }
14655
- return (jsx(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, footer: footerNav, children: jsxs("div", { style: { padding: "20px 10px" }, children: [jsx(FlowProgress, { currentStep: "date", hasUpsellsStep: hasUpsellsStep }), jsx("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
14656
- const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
14657
- return (jsxs(Fragment$1, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsx("div", { style: { height: "1px", backgroundColor: "var(--bw-border-color)", margin: "4px 0" } })), jsx(Accordion, { title: label, priceInfo: jsx("div", { style: {
14658
- fontSize: "16px",
14659
- fontWeight: 500,
14660
- marginLeft: "auto",
14661
- padding: "4px 8px",
14662
- borderRadius: "var(--bw-border-radius-small)",
14663
- backgroundColor: monthPriceDisplayInfo
14664
- ? monthPriceDisplayInfo.backgroundColor
14665
- : "#14532d",
14666
- color: monthPriceDisplayInfo
14667
- ? monthPriceDisplayInfo.textColor
14668
- : "#4ade80",
14669
- boxShadow: monthPriceDisplayInfo
14670
- ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14671
- : undefined,
14672
- }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14673
- const availableSpots = event.maxParticipants - event.participantCount;
14674
- const isFullyBooked = availableSpots === 0;
14675
- const startDate = new Date(event.startTime);
14676
- const isPastEvent = today.toISOString() >= startDate.toISOString();
14677
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14678
- return (jsxs("div", { style: {
14679
- position: "relative",
14680
- border: "1px solid var(--bw-border-color)",
14681
- backgroundColor: "var(--bw-surface-color)",
14682
- borderRadius: "var(--bw-border-radius)",
14683
- padding: "16px 10px",
14684
- transition: "all 0.2s ease",
14685
- fontFamily: "var(--bw-font-family)",
14686
- opacity: isDisabled ? 0.3 : 1,
14687
- filter: isDisabled ? "grayscale(40%)" : "none",
14688
- cursor: isDisabled ? "not-allowed" : "pointer",
14689
- }, onClick: () => {
14690
- if (!isDisabled) {
14691
- handleEventInstanceSelect(event);
14692
- }
14693
- }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
14694
- position: "absolute",
14695
- inset: 0,
14696
- backgroundColor: "rgba(15, 23, 42, 0.8)",
14697
- borderRadius: "var(--bw-border-radius)",
14698
- display: "flex",
14699
- alignItems: "center",
14700
- justifyContent: "center",
14701
- }, children: jsx("div", { style: {
14702
- width: "32px",
14703
- height: "32px",
14704
- color: "var(--bw-highlight-color)",
14705
- opacity: 0.8,
14706
- fontSize: "32px",
14707
- }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
14708
- display: "flex",
14709
- justifyContent: "space-between",
14710
- width: "100%",
14711
- alignItems: "flex-start",
14712
- gap: "12px",
14713
- marginBottom: "4px",
14714
- }, children: [jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsx("div", { style: {
14715
- fontSize: "16px",
14716
- transition: "all 0.2s ease",
14717
- borderRadius: "var(--bw-border-radius-small)",
14718
- borderTop: "4px solid var(--bw-border-color)",
14719
- border: "1px solid var(--bw-border-color)",
14720
- width: "40px",
14721
- height: "40px",
14722
- display: "flex",
14723
- alignItems: "center",
14724
- justifyContent: "center",
14725
- fontWeight: 700,
14726
- color: "var(--bw-text-color)",
14727
- backgroundColor: "var(--bw-background-color)",
14728
- }, children: startDate.getDate() }), jsxs("div", { style: {
14729
- fontSize: "16px",
14730
- color: "var(--bw-text-color)",
14731
- display: "flex",
14732
- flexDirection: "column",
14733
- alignItems: "flex-start",
14734
- justifyContent: "flex-start",
14735
- lineHeight: 1.25,
14736
- }, children: [jsxs("div", { children: [jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
14737
- formatWeekday(event.endTime, timezone, locale) && (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsx("div", { children: formatWeekday(event.startTime, timezone, locale) ===
14738
- formatWeekday(event.endTime, timezone, locale) ? (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxs("span", { style: {
14739
- fontSize: "12px",
14740
- fontWeight: 400,
14741
- color: "var(--bw-text-muted)",
14742
- marginLeft: "6px",
14743
- backgroundColor: "rgba(0, 0, 0, 0.05)",
14744
- whiteSpace: "nowrap",
14745
- padding: "2px 6px",
14746
- borderRadius: "var(--bw-border-radius-small)",
14747
- }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsx("div", { style: {
14748
- textAlign: "right",
14749
- display: "flex",
14750
- flexDirection: "column",
14751
- alignItems: "flex-end",
14752
- }, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { style: {
14753
- fontSize: "16px",
14754
- fontWeight: 600,
14755
- color: "var(--bw-text-color)",
14756
- lineHeight: 1.25,
14757
- margin: "0 0 2px 0",
14758
- display: "flex",
14759
- alignItems: "center",
14760
- gap: "8px",
14761
- maxWidth: "230px",
14762
- }, children: event.name }))] }, event.id));
14763
- }) }) })] }, key));
14764
- }) })] }) }));
14860
+ return (jsxs(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, footer: footerNav, children: [jsxs("div", { style: { padding: "20px 10px" }, children: [jsx(FlowProgress, { currentStep: "date", hasUpsellsStep: hasUpsellsStep }), jsx("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
14861
+ const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
14862
+ return (jsxs(Fragment$1, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsx("div", { style: { height: "1px", backgroundColor: "var(--bw-border-color)", margin: "4px 0" } })), jsx(Accordion, { title: label, priceInfo: jsx("div", { style: {
14863
+ fontSize: "16px",
14864
+ fontWeight: 500,
14865
+ marginLeft: "auto",
14866
+ padding: "4px 8px",
14867
+ borderRadius: "var(--bw-border-radius-small)",
14868
+ backgroundColor: monthPriceDisplayInfo
14869
+ ? monthPriceDisplayInfo.backgroundColor
14870
+ : "#14532d",
14871
+ color: monthPriceDisplayInfo
14872
+ ? monthPriceDisplayInfo.textColor
14873
+ : "#4ade80",
14874
+ boxShadow: monthPriceDisplayInfo
14875
+ ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14876
+ : undefined,
14877
+ }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14878
+ const availableSpots = event.maxParticipants - event.participantCount;
14879
+ const isFullyBooked = availableSpots === 0;
14880
+ const startDate = new Date(event.startTime);
14881
+ const isPastEvent = today.toISOString() >= startDate.toISOString();
14882
+ const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14883
+ const canWaitlist = isFullyBooked &&
14884
+ !isPastEvent &&
14885
+ !!apiBaseUrl &&
14886
+ !!organizationId;
14887
+ return (jsxs("div", { style: {
14888
+ position: "relative",
14889
+ border: "1px solid var(--bw-border-color)",
14890
+ backgroundColor: "var(--bw-surface-color)",
14891
+ borderRadius: "var(--bw-border-radius)",
14892
+ padding: "16px 10px",
14893
+ transition: "all 0.2s ease",
14894
+ fontFamily: "var(--bw-font-family)",
14895
+ opacity: isDisabled && !canWaitlist ? 0.3 : 1,
14896
+ filter: isDisabled && !canWaitlist ? "grayscale(40%)" : "none",
14897
+ cursor: isDisabled
14898
+ ? canWaitlist
14899
+ ? "default"
14900
+ : "not-allowed"
14901
+ : "pointer",
14902
+ }, onClick: () => {
14903
+ if (!isDisabled) {
14904
+ handleEventInstanceSelect(event);
14905
+ }
14906
+ }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
14907
+ position: "absolute",
14908
+ inset: 0,
14909
+ backgroundColor: "rgba(15, 23, 42, 0.8)",
14910
+ borderRadius: "var(--bw-border-radius)",
14911
+ display: "flex",
14912
+ alignItems: "center",
14913
+ justifyContent: "center",
14914
+ }, children: jsx("div", { style: {
14915
+ width: "32px",
14916
+ height: "32px",
14917
+ color: "var(--bw-highlight-color)",
14918
+ opacity: 0.8,
14919
+ fontSize: "32px",
14920
+ }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
14921
+ display: "flex",
14922
+ justifyContent: "space-between",
14923
+ width: "100%",
14924
+ alignItems: "flex-start",
14925
+ gap: "12px",
14926
+ marginBottom: "4px",
14927
+ }, children: [jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsx("div", { style: {
14928
+ fontSize: "16px",
14929
+ transition: "all 0.2s ease",
14930
+ borderRadius: "var(--bw-border-radius-small)",
14931
+ borderTop: "4px solid var(--bw-border-color)",
14932
+ border: "1px solid var(--bw-border-color)",
14933
+ width: "40px",
14934
+ height: "40px",
14935
+ display: "flex",
14936
+ alignItems: "center",
14937
+ justifyContent: "center",
14938
+ fontWeight: 700,
14939
+ color: "var(--bw-text-color)",
14940
+ backgroundColor: "var(--bw-background-color)",
14941
+ }, children: startDate.getDate() }), jsxs("div", { style: {
14942
+ fontSize: "16px",
14943
+ color: "var(--bw-text-color)",
14944
+ display: "flex",
14945
+ flexDirection: "column",
14946
+ alignItems: "flex-start",
14947
+ justifyContent: "flex-start",
14948
+ lineHeight: 1.25,
14949
+ }, children: [jsxs("div", { children: [jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
14950
+ formatWeekday(event.endTime, timezone, locale) && (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsx("div", { children: formatWeekday(event.startTime, timezone, locale) ===
14951
+ formatWeekday(event.endTime, timezone, locale) ? (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxs("span", { style: {
14952
+ fontSize: "12px",
14953
+ fontWeight: 400,
14954
+ color: "var(--bw-text-muted)",
14955
+ marginLeft: "6px",
14956
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
14957
+ whiteSpace: "nowrap",
14958
+ padding: "2px 6px",
14959
+ borderRadius: "var(--bw-border-radius-small)",
14960
+ }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsx("div", { style: {
14961
+ textAlign: "right",
14962
+ display: "flex",
14963
+ flexDirection: "column",
14964
+ alignItems: "flex-end",
14965
+ }, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { style: {
14966
+ fontSize: "16px",
14967
+ fontWeight: 600,
14968
+ color: "var(--bw-text-color)",
14969
+ lineHeight: 1.25,
14970
+ margin: "0 0 2px 0",
14971
+ display: "flex",
14972
+ alignItems: "center",
14973
+ gap: "8px",
14974
+ maxWidth: "230px",
14975
+ }, children: event.name })), canWaitlist && (jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth, buttonStyles.small, { marginTop: "8px" }), onClick: (e) => {
14976
+ e.stopPropagation();
14977
+ setWaitlistEvent(event);
14978
+ }, children: t("waitlist.joinCta") }))] }, event.id));
14979
+ }) }) })] }, key));
14980
+ }) })] }), waitlistEvent && apiBaseUrl && organizationId && (jsx(WaitlistDialog, { apiBaseUrl: apiBaseUrl, organizationId: organizationId, eventInstanceId: waitlistEvent.id, eventName: waitlistEvent.name, onClose: () => setWaitlistEvent(null) }))] }));
14765
14981
  }
14766
14982
 
14767
14983
  function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText, showAllButton, isLoadingEventDetails = false, isLoadingShowAll = false, isLoading = false, count = 3, }) {
@@ -15655,6 +15871,7 @@ let buffer = [];
15655
15871
  let flushTimer = null;
15656
15872
  let currentApiBaseUrl = "";
15657
15873
  let currentOrganizationId = "";
15874
+ let currentPartnerContractId = null;
15658
15875
  const FLUSH_INTERVAL_MS = 3000;
15659
15876
  function flush() {
15660
15877
  if (buffer.length === 0)
@@ -15696,9 +15913,10 @@ function scheduleFlush() {
15696
15913
  /**
15697
15914
  * Initialise the analytics module. Must be called once before `trackEvent`.
15698
15915
  */
15699
- function initAnalytics(apiBaseUrl, organizationId) {
15916
+ function initAnalytics(apiBaseUrl, organizationId, options) {
15700
15917
  currentApiBaseUrl = apiBaseUrl;
15701
15918
  currentOrganizationId = organizationId;
15919
+ currentPartnerContractId = options?.partnerContractId?.trim() || null;
15702
15920
  if (typeof window !== "undefined") {
15703
15921
  window.addEventListener("pagehide", flush);
15704
15922
  window.addEventListener("visibilitychange", () => {
@@ -15715,9 +15933,12 @@ function trackEvent(event, properties = {}) {
15715
15933
  return;
15716
15934
  if (!currentOrganizationId)
15717
15935
  return;
15936
+ const mergedProperties = currentPartnerContractId
15937
+ ? { ...properties, partnerContractId: currentPartnerContractId }
15938
+ : properties;
15718
15939
  buffer.push({
15719
15940
  event,
15720
- properties,
15941
+ properties: mergedProperties,
15721
15942
  domain: window.location.hostname,
15722
15943
  url: window.location.href,
15723
15944
  referrer: document.referrer,
@@ -15793,7 +16014,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15793
16014
  const [sidebarOpen, setSidebarOpen] = useState(false);
15794
16015
  // Booking flow state
15795
16016
  const [eventDetails, setEventDetails] = useState(null);
15796
- const [stripePromise, setStripePromise] = useState(null);
15797
16017
  // SEPARATED LOADING STATES
15798
16018
  const [isLoading, setIsLoading] = useState(true);
15799
16019
  const [isLoadingEventInstances, setIsLoadingEventInstances] = useState(false);
@@ -15928,7 +16148,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15928
16148
  useEffect(() => {
15929
16149
  if (!analyticsInitRef.current && config.organizationId) {
15930
16150
  analyticsInitRef.current = true;
15931
- initAnalytics(config.apiBaseUrl, config.organizationId);
16151
+ initAnalytics(config.apiBaseUrl, config.organizationId, {
16152
+ partnerContractId: config.partnerContractId,
16153
+ });
15932
16154
  trackEvent("widget_loaded", {
15933
16155
  viewMode,
15934
16156
  eventTypeId: config.eventTypeId,
@@ -15937,7 +16159,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15937
16159
  isStandaloneVoucherMode,
15938
16160
  });
15939
16161
  }
15940
- }, [config.organizationId, config.apiBaseUrl]);
16162
+ }, [config.organizationId, config.apiBaseUrl, config.partnerContractId, viewMode, config.eventTypeId, config.categoryId, config.eventInstanceId, isStandaloneVoucherMode]);
15941
16163
  // Fire widget pageview once when Google Ads config is received from API
15942
16164
  const pageviewFiredRef = useRef(false);
15943
16165
  useEffect(() => {
@@ -15951,6 +16173,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15951
16173
  const initializeWidget = async () => {
15952
16174
  try {
15953
16175
  setIsLoading(true);
16176
+ if (config.partnerContractId) {
16177
+ const validationResponse = await fetch(getApiUrl(config.apiBaseUrl, `/booking/validate-partner?organizationId=${encodeURIComponent(config.organizationId)}&contractId=${config.partnerContractId}`), {
16178
+ method: "GET",
16179
+ headers: createApiHeaders(config, locale),
16180
+ });
16181
+ if (!validationResponse.ok) {
16182
+ setError("Invalid partner configuration");
16183
+ config.onError?.("Invalid partner configuration");
16184
+ return;
16185
+ }
16186
+ const validationData = (await validationResponse.json());
16187
+ if (!validationData.valid) {
16188
+ setError("Invalid partner configuration");
16189
+ config.onError?.("Invalid partner configuration");
16190
+ return;
16191
+ }
16192
+ }
15954
16193
  // Load voucher config in parallel if voucher mode is requested or there's event selection
15955
16194
  if (isVoucherModeRequested || hasEventSelection) {
15956
16195
  void loadVoucherConfig();
@@ -16086,11 +16325,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16086
16325
  setVoucherPurchaseResult(voucherData.voucherResult);
16087
16326
  setIsSuccess(true);
16088
16327
  setSuccessPaymentId(null);
16328
+ return;
16089
16329
  }
16090
16330
  }
16091
16331
  catch {
16092
- // Ignore lookup errors; webhook eventual consistency may delay data.
16093
- }
16332
+ // Fall back to booking success flow if voucher lookup fails.
16333
+ }
16334
+ // Not a voucher purchase: treat as a booking return and open the
16335
+ // booking confirmation, mirroring the Stripe redirect handler.
16336
+ trackEvent("booking_completed", { paymentIntentId: mollieReturn.molliePaymentId, source: "mollie_redirect" });
16337
+ setVoucherPurchaseResult(null);
16338
+ setSuccessPaymentId(mollieReturn.molliePaymentId);
16339
+ setIsSuccess(true);
16094
16340
  };
16095
16341
  if (!isLoading) {
16096
16342
  void handleMollieVoucherReturn();
@@ -16279,27 +16525,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16279
16525
  widgetPaymentMode: data.widgetPaymentMode ?? "all",
16280
16526
  });
16281
16527
  }
16282
- if (data.stripePublishableKey) {
16283
- const stripeOptions = {
16284
- locale: locale,
16285
- };
16286
- if (data.connectedAccountId) {
16287
- stripeOptions.stripeAccount = data.connectedAccountId;
16288
- }
16289
- if (typeof window !== "undefined" && window.document) {
16290
- try {
16291
- void (window.parent === window || window.parent.location.href);
16292
- if (!stripeOptions.betas) {
16293
- stripeOptions.betas = [];
16294
- }
16295
- }
16296
- catch {
16297
- console.warn("[WIDGET] Detected restricted environment, adjusting Stripe options");
16298
- stripeOptions.betas = [];
16299
- }
16300
- }
16301
- setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16302
- }
16303
16528
  if (!skipInstanceAutoSelectRef.current && data.eventInstances.length === 1) {
16304
16529
  setSelectedEventInstance(data.eventInstances[0]);
16305
16530
  setCurrentStep("booking");
@@ -16339,29 +16564,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16339
16564
  roundPricesEnabled: data.roundPricesEnabled ?? true,
16340
16565
  widgetPaymentMode: data.widgetPaymentMode ?? "all",
16341
16566
  });
16342
- if (!stripePromise && data.stripePublishableKey) {
16343
- const stripeOptions = {
16344
- locale: locale,
16345
- };
16346
- if (data.connectedAccountId) {
16347
- stripeOptions.stripeAccount = data.connectedAccountId;
16348
- }
16349
- if (typeof window !== "undefined" && window.document) {
16350
- try {
16351
- // Check if we have parent access (not in a cross-origin iframe)
16352
- void (window.parent === window || window.parent.location.href);
16353
- stripeOptions.apiVersion = "2025-02-24.acacia";
16354
- if (!stripeOptions.betas) {
16355
- stripeOptions.betas = [];
16356
- }
16357
- }
16358
- catch {
16359
- console.warn("[WIDGET] Detected restricted environment, adjusting Stripe options");
16360
- stripeOptions.betas = [];
16361
- }
16362
- }
16363
- setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16364
- }
16365
16567
  }
16366
16568
  else {
16367
16569
  const errorMessage = data.error || t("error.loadEventDetails");
@@ -16630,12 +16832,12 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16630
16832
  startTime: cardPreviewItem.startTime,
16631
16833
  endTime: cardPreviewItem.startTime,
16632
16834
  price: cardPreviewItem.price,
16633
- maxParticipants: cardPreviewItem.availableSpots + 1,
16634
- participantCount: 1,
16835
+ maxParticipants: Math.max(cardPreviewItem.availableSpots, 0),
16836
+ participantCount: 0,
16635
16837
  availableSpots: cardPreviewItem.availableSpots,
16636
16838
  durationDays: 1,
16637
16839
  durationPerDay: 1,
16638
- bookingOpen: true,
16840
+ bookingOpen: cardPreviewItem.bookingOpen && cardPreviewItem.availableSpots > 0,
16639
16841
  }
16640
16842
  : null;
16641
16843
  if (eventInstance) {
@@ -16823,7 +17025,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16823
17025
  }
16824
17026
  // Main view based on view mode
16825
17027
  if (viewMode === "next-events" && showingPreview) {
16826
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [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 && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17028
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [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 && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16827
17029
  setIsSuccess(false);
16828
17030
  setCurrentStep("eventTypes");
16829
17031
  setShowingPreview(true);
@@ -16844,7 +17046,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16844
17046
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16845
17047
  }
16846
17048
  if (viewMode === "specials" && showingPreview) {
16847
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17049
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16848
17050
  setIsSuccess(false);
16849
17051
  setCurrentStep("eventTypes");
16850
17052
  setShowingPreview(true);
@@ -16864,7 +17066,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16864
17066
  }, isOpen: currentStep === "eventInstances", onClose: () => {
16865
17067
  setShowingPreview(true);
16866
17068
  setCurrentStep("eventTypes");
16867
- }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17069
+ }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16868
17070
  setIsSuccess(false);
16869
17071
  setCurrentStep("eventTypes");
16870
17072
  setShowingPreview(true);
@@ -16920,7 +17122,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16920
17122
  setShouldRenderInstanceSelection(true);
16921
17123
  }
16922
17124
  }, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
16923
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (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 && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17125
+ (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (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 && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16924
17126
  setIsSuccess(false);
16925
17127
  setCurrentStep("eventTypes");
16926
17128
  setSidebarOpen(false);
@@ -16981,7 +17183,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16981
17183
  };
16982
17184
  };
16983
17185
  const backHandlers = getBackHandlers();
16984
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17186
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (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 && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
16985
17187
  setIsSuccess(false);
16986
17188
  setCurrentStep("eventTypes");
16987
17189
  setSuccessPaymentId(null);