@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.
- package/dist/booking-widget.js +17856 -17103
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts +30 -4
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- package/dist/components/booking/BookingForm.d.ts +1 -3
- package/dist/components/booking/BookingForm.d.ts.map +1 -1
- package/dist/components/booking/GiftCardOnlyBooking.d.ts.map +1 -1
- package/dist/components/booking/HoldCountdown.d.ts +10 -0
- package/dist/components/booking/HoldCountdown.d.ts.map +1 -0
- package/dist/components/booking/MolliePaymentForm.d.ts +11 -1
- package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
- package/dist/components/booking/StripePaymentForm.d.ts +11 -2
- package/dist/components/booking/StripePaymentForm.d.ts.map +1 -1
- package/dist/components/events/EventInstanceSelection.d.ts +3 -1
- package/dist/components/events/EventInstanceSelection.d.ts.map +1 -1
- package/dist/components/events/EventTypeDetailsDialog.d.ts +0 -1
- package/dist/components/events/EventTypeDetailsDialog.d.ts.map +1 -1
- package/dist/components/events/EventTypeSelection.d.ts +16 -1
- package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
- package/dist/components/events/FreeformSelection.d.ts +49 -0
- package/dist/components/events/FreeformSelection.d.ts.map +1 -0
- package/dist/components/events/WaitlistDialog.d.ts +10 -0
- package/dist/components/events/WaitlistDialog.d.ts.map +1 -0
- package/dist/components/events/index.d.ts +1 -0
- package/dist/components/events/index.d.ts.map +1 -1
- package/dist/components/voucher/VoucherIntegration.d.ts +3 -2
- package/dist/components/voucher/VoucherIntegration.d.ts.map +1 -1
- package/dist/components/voucher/VoucherPurchaseForm.d.ts +8 -3
- package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
- package/dist/components/voucher/index.d.ts +1 -1
- package/dist/components/voucher/index.d.ts.map +1 -1
- package/dist/components/voucher/useVoucherConfig.d.ts +6 -1
- package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/index.cjs +1235 -482
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1228 -475
- package/dist/index.esm.js.map +1 -1
- package/dist/utils/analytics.d.ts +3 -1
- package/dist/utils/analytics.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/validation/booking-schema.d.ts +4 -4
- 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",
|
|
@@ -295,6 +149,10 @@ const de$1 = {
|
|
|
295
149
|
"booking.stepUpsells": "Extras",
|
|
296
150
|
"booking.stepDetails": "Angaben",
|
|
297
151
|
"booking.stepPayment": "Zahlung",
|
|
152
|
+
"booking.steps": "{{count}} Schritt(e)",
|
|
153
|
+
"booking.freeformUnitsAvailable": "{{count}} Einheiten verfuegbar",
|
|
154
|
+
"booking.freeformNotAvailable": "Das gewaehlte Zeitfenster ist nicht verfuegbar",
|
|
155
|
+
"booking.freeformMissingConfig": "Die Freeform-Konfiguration fuer diesen Event-Typ fehlt.",
|
|
298
156
|
// Booking summary
|
|
299
157
|
"summary.title": "Buchungszusammenfassung",
|
|
300
158
|
"summary.subtotal": "Zwischensumme:",
|
|
@@ -350,6 +208,13 @@ const de$1 = {
|
|
|
350
208
|
"voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
|
|
351
209
|
"voucher.eventType": "Erlebnisgutschein",
|
|
352
210
|
"voucher.eventTypeDesc": "Für ein bestimmtes Erlebnis",
|
|
211
|
+
"voucher.categoryType": "Kategoriegutschein",
|
|
212
|
+
"voucher.categoryTypeDesc": "Für ein beliebiges Erlebnis einer Kategorie",
|
|
213
|
+
"voucher.categoryVoucher": "Kategoriegutschein",
|
|
214
|
+
"voucher.selectCategory": "Kategorie wählen",
|
|
215
|
+
"voucher.categoryLabel": "Kategorie",
|
|
216
|
+
"voucher.selectCategoryPlaceholder": "Kategorie auswählen...",
|
|
217
|
+
"voucher.categoryAmount": "Gutscheinwert",
|
|
353
218
|
"voucher.selectAmount": "Betrag wählen",
|
|
354
219
|
"voucher.customAmount": "Oder eigenen Betrag eingeben",
|
|
355
220
|
"voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
|
|
@@ -537,6 +402,24 @@ const en = {
|
|
|
537
402
|
"instances.bestPrice": "best price !!!",
|
|
538
403
|
"instances.goodPrice": "good price",
|
|
539
404
|
"instances.oclock": "",
|
|
405
|
+
// Waitlist
|
|
406
|
+
"waitlist.joinCta": "Join waitlist",
|
|
407
|
+
"waitlist.title": "Join the waitlist",
|
|
408
|
+
"waitlist.subtitle": "{{name}} is fully booked. Leave your details and we'll contact you if a spot opens up.",
|
|
409
|
+
"waitlist.name": "Name",
|
|
410
|
+
"waitlist.email": "Email",
|
|
411
|
+
"waitlist.phone": "Phone (optional)",
|
|
412
|
+
"waitlist.requiredFields": "Please enter your name and email.",
|
|
413
|
+
"waitlist.join": "Join waitlist",
|
|
414
|
+
"waitlist.submitting": "Joining...",
|
|
415
|
+
"waitlist.cancel": "Cancel",
|
|
416
|
+
"waitlist.submitError": "Something went wrong. Please try again.",
|
|
417
|
+
"waitlist.successTitle": "You're on the list!",
|
|
418
|
+
"waitlist.successMessage": "You are number {{position}} on the waitlist. We'll be in touch if a spot becomes available.",
|
|
419
|
+
"waitlist.done": "Done",
|
|
420
|
+
// Slot hold countdown
|
|
421
|
+
"hold.reservedFor": "Slot reserved for {{time}}",
|
|
422
|
+
"hold.expired": "Your reservation has expired",
|
|
540
423
|
// Next events preview
|
|
541
424
|
"nextEvents.title": "Next available dates",
|
|
542
425
|
"nextEvents.subtitle": "Select a date or view all available dates",
|
|
@@ -585,6 +468,10 @@ const en = {
|
|
|
585
468
|
"booking.stepUpsells": "Extras",
|
|
586
469
|
"booking.stepDetails": "Details",
|
|
587
470
|
"booking.stepPayment": "Payment",
|
|
471
|
+
"booking.steps": "{{count}} step(s)",
|
|
472
|
+
"booking.freeformUnitsAvailable": "{{count}} units available",
|
|
473
|
+
"booking.freeformNotAvailable": "Selected window is not available",
|
|
474
|
+
"booking.freeformMissingConfig": "Missing freeform configuration for this event type.",
|
|
588
475
|
// Booking summary
|
|
589
476
|
"summary.title": "Booking Summary",
|
|
590
477
|
"summary.subtotal": "Subtotal:",
|
|
@@ -640,6 +527,13 @@ const en = {
|
|
|
640
527
|
"voucher.monetaryTypeDesc": "Choose any amount",
|
|
641
528
|
"voucher.eventType": "Event Voucher",
|
|
642
529
|
"voucher.eventTypeDesc": "For a specific experience",
|
|
530
|
+
"voucher.categoryType": "Category Voucher",
|
|
531
|
+
"voucher.categoryTypeDesc": "For any experience in a category",
|
|
532
|
+
"voucher.categoryVoucher": "Category Voucher",
|
|
533
|
+
"voucher.selectCategory": "Select Category",
|
|
534
|
+
"voucher.categoryLabel": "Category",
|
|
535
|
+
"voucher.selectCategoryPlaceholder": "Choose a category...",
|
|
536
|
+
"voucher.categoryAmount": "Voucher Value",
|
|
643
537
|
"voucher.selectAmount": "Select Amount",
|
|
644
538
|
"voucher.customAmount": "Or enter custom amount",
|
|
645
539
|
"voucher.amountRange": "Amount must be between {{min}} and {{max}}",
|
|
@@ -930,6 +824,13 @@ const es = {
|
|
|
930
824
|
"voucher.monetaryTypeDesc": "Elige cualquier cantidad",
|
|
931
825
|
"voucher.eventType": "Vale de experiencia",
|
|
932
826
|
"voucher.eventTypeDesc": "Para una experiencia específica",
|
|
827
|
+
"voucher.categoryType": "Vale de categoría",
|
|
828
|
+
"voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
|
|
829
|
+
"voucher.categoryVoucher": "Vale de categoría",
|
|
830
|
+
"voucher.selectCategory": "Seleccionar categoría",
|
|
831
|
+
"voucher.categoryLabel": "Categoría",
|
|
832
|
+
"voucher.selectCategoryPlaceholder": "Elige una categoría...",
|
|
833
|
+
"voucher.categoryAmount": "Valor del vale",
|
|
933
834
|
"voucher.selectAmount": "Selecciona el importe",
|
|
934
835
|
"voucher.customAmount": "O introduce un importe personalizado",
|
|
935
836
|
"voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
|
|
@@ -1220,6 +1121,13 @@ const pt = {
|
|
|
1220
1121
|
"voucher.monetaryTypeDesc": "Escolha qualquer quantia",
|
|
1221
1122
|
"voucher.eventType": "Vale de experiência",
|
|
1222
1123
|
"voucher.eventTypeDesc": "Para uma experiência específica",
|
|
1124
|
+
"voucher.categoryType": "Vale de categoria",
|
|
1125
|
+
"voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
|
|
1126
|
+
"voucher.categoryVoucher": "Vale de categoria",
|
|
1127
|
+
"voucher.selectCategory": "Selecionar categoria",
|
|
1128
|
+
"voucher.categoryLabel": "Categoria",
|
|
1129
|
+
"voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
|
|
1130
|
+
"voucher.categoryAmount": "Valor do vale",
|
|
1223
1131
|
"voucher.selectAmount": "Selecione o valor",
|
|
1224
1132
|
"voucher.customAmount": "Ou introduza um valor personalizado",
|
|
1225
1133
|
"voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
|
|
@@ -1510,6 +1418,13 @@ const sv = {
|
|
|
1510
1418
|
"voucher.monetaryTypeDesc": "Välj valfritt belopp",
|
|
1511
1419
|
"voucher.eventType": "Upplevelsebevis",
|
|
1512
1420
|
"voucher.eventTypeDesc": "För en specifik upplevelse",
|
|
1421
|
+
"voucher.categoryType": "Kategoripresent",
|
|
1422
|
+
"voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
|
|
1423
|
+
"voucher.categoryVoucher": "Kategoripresent",
|
|
1424
|
+
"voucher.selectCategory": "Välj kategori",
|
|
1425
|
+
"voucher.categoryLabel": "Kategori",
|
|
1426
|
+
"voucher.selectCategoryPlaceholder": "Välj en kategori...",
|
|
1427
|
+
"voucher.categoryAmount": "Presentkortsvärde",
|
|
1513
1428
|
"voucher.selectAmount": "Välj belopp",
|
|
1514
1429
|
"voucher.customAmount": "Eller ange eget belopp",
|
|
1515
1430
|
"voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
|
|
@@ -2242,6 +2157,7 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2242
2157
|
const categoryId = urlParams.get("categoryId") || urlParams.get("category");
|
|
2243
2158
|
const eventTypeId = urlParams.get("eventTypeId") || urlParams.get("type");
|
|
2244
2159
|
const eventTypeIds = urlParams.get("eventTypeIds") || urlParams.get("types");
|
|
2160
|
+
const partnerContractId = urlParams.get("partnerContractId") || urlParams.get("contractId");
|
|
2245
2161
|
const voucherValue = urlParams.get("voucherValue");
|
|
2246
2162
|
const voucherCategory = urlParams.get("voucherCategory");
|
|
2247
2163
|
const voucherEventType = urlParams.get("voucherEventType");
|
|
@@ -2254,11 +2170,14 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2254
2170
|
: ["0", "false", "no", "off"].includes(voucherCardIntegration.toLowerCase())
|
|
2255
2171
|
? false
|
|
2256
2172
|
: undefined;
|
|
2257
|
-
const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, ...rest }) => rest;
|
|
2173
|
+
const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, partnerContractId: _pc, ...rest }) => rest;
|
|
2258
2174
|
if (eventInstanceId && !Number.isNaN(Number(eventInstanceId))) {
|
|
2259
2175
|
return {
|
|
2260
2176
|
...omitSelectionKeys(baseConfig),
|
|
2261
2177
|
eventInstanceId: Number(eventInstanceId),
|
|
2178
|
+
...(partnerContractId
|
|
2179
|
+
? { partnerContractId }
|
|
2180
|
+
: {}),
|
|
2262
2181
|
...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
|
|
2263
2182
|
};
|
|
2264
2183
|
}
|
|
@@ -2266,6 +2185,9 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2266
2185
|
return {
|
|
2267
2186
|
...omitSelectionKeys(baseConfig),
|
|
2268
2187
|
categoryId: Number(categoryId),
|
|
2188
|
+
...(partnerContractId
|
|
2189
|
+
? { partnerContractId }
|
|
2190
|
+
: {}),
|
|
2269
2191
|
...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
|
|
2270
2192
|
};
|
|
2271
2193
|
}
|
|
@@ -2273,6 +2195,9 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2273
2195
|
return {
|
|
2274
2196
|
...omitSelectionKeys(baseConfig),
|
|
2275
2197
|
eventTypeId: Number(eventTypeId),
|
|
2198
|
+
...(partnerContractId
|
|
2199
|
+
? { partnerContractId }
|
|
2200
|
+
: {}),
|
|
2276
2201
|
...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
|
|
2277
2202
|
};
|
|
2278
2203
|
}
|
|
@@ -2285,6 +2210,9 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2285
2210
|
return {
|
|
2286
2211
|
...omitSelectionKeys(baseConfig),
|
|
2287
2212
|
eventTypeIds: typeIds,
|
|
2213
|
+
...(partnerContractId
|
|
2214
|
+
? { partnerContractId }
|
|
2215
|
+
: {}),
|
|
2288
2216
|
...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
|
|
2289
2217
|
};
|
|
2290
2218
|
}
|
|
@@ -2301,6 +2229,9 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2301
2229
|
if (parsedVoucherValues.length || parsedVoucherCategories.length || parsedVoucherEventTypes.length) {
|
|
2302
2230
|
return {
|
|
2303
2231
|
...baseConfig,
|
|
2232
|
+
...(partnerContractId
|
|
2233
|
+
? { partnerContractId }
|
|
2234
|
+
: {}),
|
|
2304
2235
|
...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
|
|
2305
2236
|
...(parsedVoucherValues.length > 0
|
|
2306
2237
|
? { voucherValue: parsedVoucherValues.length === 1 ? parsedVoucherValues[0] : parsedVoucherValues }
|
|
@@ -2318,6 +2249,9 @@ const inferConfigFromUrl = (baseConfig) => {
|
|
|
2318
2249
|
}
|
|
2319
2250
|
return {
|
|
2320
2251
|
...baseConfig,
|
|
2252
|
+
...(partnerContractId
|
|
2253
|
+
? { partnerContractId }
|
|
2254
|
+
: {}),
|
|
2321
2255
|
...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
|
|
2322
2256
|
};
|
|
2323
2257
|
};
|
|
@@ -6330,6 +6264,7 @@ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, gif
|
|
|
6330
6264
|
customerPhone: formData.customerPhone?.trim(),
|
|
6331
6265
|
comment: formData.comment?.trim(),
|
|
6332
6266
|
paymentMethod: "gift_card",
|
|
6267
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6333
6268
|
...(upsellSelections.length > 0 && { upsellSelections }),
|
|
6334
6269
|
};
|
|
6335
6270
|
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
|
|
@@ -6460,7 +6395,7 @@ function loadMollieScript() {
|
|
|
6460
6395
|
document.head.appendChild(script);
|
|
6461
6396
|
});
|
|
6462
6397
|
}
|
|
6463
|
-
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
|
|
6398
|
+
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
|
|
6464
6399
|
const t = useTranslations();
|
|
6465
6400
|
const { locale } = useLocale();
|
|
6466
6401
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -6474,7 +6409,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6474
6409
|
const cardFormRef = useRef(null);
|
|
6475
6410
|
const cardContainerRef = useRef(null);
|
|
6476
6411
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6477
|
-
const isFullyCoveredByGiftCards =
|
|
6412
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
6413
|
+
? false
|
|
6414
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
6478
6415
|
const isCreditCard = selectedMethod === "creditcard";
|
|
6479
6416
|
useEffect(() => {
|
|
6480
6417
|
if (isFullyCoveredByGiftCards)
|
|
@@ -6584,6 +6521,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6584
6521
|
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6585
6522
|
}
|
|
6586
6523
|
const validateBeforePayment = () => {
|
|
6524
|
+
if (freeformSelection) {
|
|
6525
|
+
return null;
|
|
6526
|
+
}
|
|
6587
6527
|
const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
|
|
6588
6528
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6589
6529
|
if (participantCount > availableSpots) {
|
|
@@ -6595,20 +6535,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6595
6535
|
return null;
|
|
6596
6536
|
};
|
|
6597
6537
|
const buildRequestData = (cardToken) => ({
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6538
|
+
...(freeformSelection
|
|
6539
|
+
? {
|
|
6540
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
6541
|
+
organizationId: config.organizationId,
|
|
6542
|
+
start: freeformSelection.start,
|
|
6543
|
+
end: freeformSelection.end,
|
|
6544
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
6545
|
+
amount: Math.round(totalAmount),
|
|
6546
|
+
currency: "EUR",
|
|
6547
|
+
customerName: freeformSelection.customerName.trim(),
|
|
6548
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
6549
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
6550
|
+
comment: freeformSelection.comment?.trim(),
|
|
6551
|
+
locale,
|
|
6552
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6553
|
+
...(cardToken && { cardToken }),
|
|
6554
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6555
|
+
}
|
|
6556
|
+
: {
|
|
6557
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
6558
|
+
organizationId: config.organizationId,
|
|
6559
|
+
amount: Math.round(totalAmount),
|
|
6560
|
+
currency: "EUR",
|
|
6561
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
6562
|
+
discountCode: discountCode?.code,
|
|
6563
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
6564
|
+
customerName: formData.customerName?.trim(),
|
|
6565
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
6566
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
6567
|
+
comment: formData.comment?.trim(),
|
|
6568
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6569
|
+
...(cardToken && { cardToken }),
|
|
6570
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6571
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
6572
|
+
}),
|
|
6612
6573
|
});
|
|
6613
6574
|
const handleCardPayment = async () => {
|
|
6614
6575
|
if (!mollieRef.current)
|
|
@@ -6626,7 +6587,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6626
6587
|
setPaymentError(tokenError?.message || t("error.createPayment"));
|
|
6627
6588
|
return;
|
|
6628
6589
|
}
|
|
6629
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6590
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6591
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6592
|
+
: "/booking/create-mollie-payment"), {
|
|
6630
6593
|
method: "POST",
|
|
6631
6594
|
headers: createApiHeaders(config, locale),
|
|
6632
6595
|
body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
|
|
@@ -6662,7 +6625,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6662
6625
|
setPaymentError(validationError);
|
|
6663
6626
|
return;
|
|
6664
6627
|
}
|
|
6665
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6628
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6629
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6630
|
+
: "/booking/create-mollie-payment"), {
|
|
6666
6631
|
method: "POST",
|
|
6667
6632
|
headers: createApiHeaders(config, locale),
|
|
6668
6633
|
body: JSON.stringify(createRequestBody(config, buildRequestData())),
|
|
@@ -6690,7 +6655,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6690
6655
|
? t("button.processingPayment")
|
|
6691
6656
|
: selectedMethodData
|
|
6692
6657
|
? t("payment.payWithMethod", { method: selectedMethodData.description })
|
|
6693
|
-
: totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6658
|
+
: !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6694
6659
|
? t("button.depositAndBook")
|
|
6695
6660
|
: t("button.bookNow");
|
|
6696
6661
|
const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
|
|
@@ -6832,73 +6797,257 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6832
6797
|
}, children: isLoading ? (jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", buttonLabel] })) : (buttonLabel) }))] }));
|
|
6833
6798
|
}
|
|
6834
6799
|
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
}
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6800
|
+
var V3_URL = 'https://js.stripe.com/v3';
|
|
6801
|
+
var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
|
|
6802
|
+
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';
|
|
6803
|
+
var findScript = function findScript() {
|
|
6804
|
+
var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
|
|
6805
|
+
|
|
6806
|
+
for (var i = 0; i < scripts.length; i++) {
|
|
6807
|
+
var script = scripts[i];
|
|
6808
|
+
|
|
6809
|
+
if (!V3_URL_REGEX.test(script.src)) {
|
|
6810
|
+
continue;
|
|
6811
|
+
}
|
|
6812
|
+
|
|
6813
|
+
return script;
|
|
6814
|
+
}
|
|
6815
|
+
|
|
6816
|
+
return null;
|
|
6817
|
+
};
|
|
6818
|
+
|
|
6819
|
+
var injectScript = function injectScript(params) {
|
|
6820
|
+
var queryString = '';
|
|
6821
|
+
var script = document.createElement('script');
|
|
6822
|
+
script.src = "".concat(V3_URL).concat(queryString);
|
|
6823
|
+
var headOrBody = document.head || document.body;
|
|
6824
|
+
|
|
6825
|
+
if (!headOrBody) {
|
|
6826
|
+
throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
headOrBody.appendChild(script);
|
|
6830
|
+
return script;
|
|
6831
|
+
};
|
|
6832
|
+
|
|
6833
|
+
var registerWrapper = function registerWrapper(stripe, startTime) {
|
|
6834
|
+
if (!stripe || !stripe._registerWrapper) {
|
|
6835
|
+
return;
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
stripe._registerWrapper({
|
|
6839
|
+
name: 'stripe-js',
|
|
6840
|
+
version: "4.6.0",
|
|
6841
|
+
startTime: startTime
|
|
6842
|
+
});
|
|
6843
|
+
};
|
|
6844
|
+
|
|
6845
|
+
var stripePromise = null;
|
|
6846
|
+
var onErrorListener = null;
|
|
6847
|
+
var onLoadListener = null;
|
|
6848
|
+
|
|
6849
|
+
var onError = function onError(reject) {
|
|
6850
|
+
return function () {
|
|
6851
|
+
reject(new Error('Failed to load Stripe.js'));
|
|
6852
|
+
};
|
|
6853
|
+
};
|
|
6854
|
+
|
|
6855
|
+
var onLoad = function onLoad(resolve, reject) {
|
|
6856
|
+
return function () {
|
|
6857
|
+
if (window.Stripe) {
|
|
6858
|
+
resolve(window.Stripe);
|
|
6859
|
+
} else {
|
|
6860
|
+
reject(new Error('Stripe.js not available'));
|
|
6861
|
+
}
|
|
6862
|
+
};
|
|
6863
|
+
};
|
|
6864
|
+
|
|
6865
|
+
var loadScript = function loadScript(params) {
|
|
6866
|
+
// Ensure that we only attempt to load Stripe.js at most once
|
|
6867
|
+
if (stripePromise !== null) {
|
|
6868
|
+
return stripePromise;
|
|
6869
|
+
}
|
|
6870
|
+
|
|
6871
|
+
stripePromise = new Promise(function (resolve, reject) {
|
|
6872
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
6873
|
+
// Resolve to null when imported server side. This makes the module
|
|
6874
|
+
// safe to import in an isomorphic code base.
|
|
6875
|
+
resolve(null);
|
|
6876
|
+
return;
|
|
6877
|
+
}
|
|
6878
|
+
|
|
6879
|
+
if (window.Stripe) {
|
|
6880
|
+
resolve(window.Stripe);
|
|
6881
|
+
return;
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6884
|
+
try {
|
|
6885
|
+
var script = findScript();
|
|
6886
|
+
|
|
6887
|
+
if (script && params) ; else if (!script) {
|
|
6888
|
+
script = injectScript(params);
|
|
6889
|
+
} else if (script && onLoadListener !== null && onErrorListener !== null) {
|
|
6890
|
+
var _script$parentNode;
|
|
6891
|
+
|
|
6892
|
+
// remove event listeners
|
|
6893
|
+
script.removeEventListener('load', onLoadListener);
|
|
6894
|
+
script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
|
|
6895
|
+
// reload script to trigger 'load' event
|
|
6896
|
+
|
|
6897
|
+
(_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
|
|
6898
|
+
script = injectScript(params);
|
|
6899
|
+
}
|
|
6900
|
+
|
|
6901
|
+
onLoadListener = onLoad(resolve, reject);
|
|
6902
|
+
onErrorListener = onError(reject);
|
|
6903
|
+
script.addEventListener('load', onLoadListener);
|
|
6904
|
+
script.addEventListener('error', onErrorListener);
|
|
6905
|
+
} catch (error) {
|
|
6906
|
+
reject(error);
|
|
6907
|
+
return;
|
|
6908
|
+
}
|
|
6909
|
+
}); // Resets stripePromise on error
|
|
6910
|
+
|
|
6911
|
+
return stripePromise["catch"](function (error) {
|
|
6912
|
+
stripePromise = null;
|
|
6913
|
+
return Promise.reject(error);
|
|
6914
|
+
});
|
|
6915
|
+
};
|
|
6916
|
+
var initStripe = function initStripe(maybeStripe, args, startTime) {
|
|
6917
|
+
if (maybeStripe === null) {
|
|
6918
|
+
return null;
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
var stripe = maybeStripe.apply(undefined, args);
|
|
6922
|
+
registerWrapper(stripe, startTime);
|
|
6923
|
+
return stripe;
|
|
6924
|
+
}; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
6925
|
+
|
|
6926
|
+
var stripePromise$1;
|
|
6927
|
+
var loadCalled = false;
|
|
6928
|
+
|
|
6929
|
+
var getStripePromise = function getStripePromise() {
|
|
6930
|
+
if (stripePromise$1) {
|
|
6931
|
+
return stripePromise$1;
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6934
|
+
stripePromise$1 = loadScript(null)["catch"](function (error) {
|
|
6935
|
+
// clear cache on error
|
|
6936
|
+
stripePromise$1 = null;
|
|
6937
|
+
return Promise.reject(error);
|
|
6938
|
+
});
|
|
6939
|
+
return stripePromise$1;
|
|
6940
|
+
}; // Execute our own script injection after a tick to give users time to do their
|
|
6941
|
+
// own script injection.
|
|
6942
|
+
|
|
6943
|
+
|
|
6944
|
+
Promise.resolve().then(function () {
|
|
6945
|
+
return getStripePromise();
|
|
6946
|
+
})["catch"](function (error) {
|
|
6947
|
+
if (!loadCalled) {
|
|
6948
|
+
console.warn(error);
|
|
6949
|
+
}
|
|
6950
|
+
});
|
|
6951
|
+
var loadStripe = function loadStripe() {
|
|
6952
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
6953
|
+
args[_key] = arguments[_key];
|
|
6954
|
+
}
|
|
6955
|
+
|
|
6956
|
+
loadCalled = true;
|
|
6957
|
+
var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
|
|
6958
|
+
|
|
6959
|
+
return getStripePromise().then(function (maybeStripe) {
|
|
6960
|
+
return initStripe(maybeStripe, args, startTime);
|
|
6961
|
+
});
|
|
6962
|
+
};
|
|
6963
|
+
|
|
6964
|
+
// Inner component that uses the Stripe hooks
|
|
6965
|
+
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
|
|
6966
|
+
const t = useTranslations();
|
|
6967
|
+
const isFreeform = Boolean(freeformSelection);
|
|
6968
|
+
const participantCount = isFreeform
|
|
6969
|
+
? 1
|
|
6970
|
+
: formData.participants.filter((p) => p.name.trim()).length;
|
|
6971
|
+
const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
|
|
6972
|
+
const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
|
|
6973
|
+
return (jsx(StripeElementsPaymentForm, { onValidate: () => {
|
|
6974
|
+
if (isFreeform) {
|
|
6975
|
+
return null;
|
|
6976
|
+
}
|
|
6977
|
+
const availableSpots = eventDetails.availableSpots || 0;
|
|
6978
|
+
if (participantCount > availableSpots) {
|
|
6979
|
+
return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
|
|
6980
|
+
}
|
|
6981
|
+
if (participantCount === 0) {
|
|
6982
|
+
return t("payment.needOneParticipant");
|
|
6983
|
+
}
|
|
6984
|
+
return null;
|
|
6985
|
+
}, onSuccess: (paymentIntent) => onSuccess({
|
|
6986
|
+
paymentIntent,
|
|
6987
|
+
}), onError: onError, paymentElementOptions: {
|
|
6988
|
+
layout: "accordion",
|
|
6989
|
+
defaultValues: {
|
|
6990
|
+
billingDetails: {
|
|
6991
|
+
name: formData.customerName,
|
|
6992
|
+
email: formData.customerEmail,
|
|
6993
|
+
phone: formData.customerPhone,
|
|
6994
|
+
},
|
|
6995
|
+
},
|
|
6996
|
+
}, submitContent: jsx(Fragment, { children: submitLabel }), loadingContent: jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
|
|
6997
|
+
}
|
|
6998
|
+
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
|
|
6999
|
+
const t = useTranslations();
|
|
7000
|
+
const { locale } = useLocale();
|
|
7001
|
+
const [clientSecret, setClientSecret] = useState(null);
|
|
7002
|
+
const [paymentIntentId, setPaymentIntentId] = useState(null);
|
|
7003
|
+
const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = useState(false);
|
|
7004
|
+
const [paymentError, setPaymentError] = useState(null);
|
|
7005
|
+
const stripePromise = useMemo(() => {
|
|
7006
|
+
if (!systemConfig?.stripePublishableKey) {
|
|
7007
|
+
return null;
|
|
7008
|
+
}
|
|
7009
|
+
const stripeOptions = {
|
|
7010
|
+
locale: locale,
|
|
7011
|
+
};
|
|
7012
|
+
if (systemConfig.connectedAccountId) {
|
|
7013
|
+
stripeOptions.stripeAccount = systemConfig.connectedAccountId;
|
|
7014
|
+
}
|
|
7015
|
+
return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
|
|
7016
|
+
}, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
|
|
7017
|
+
const storageKey = typeof window !== "undefined"
|
|
7018
|
+
? freeformSelection
|
|
7019
|
+
? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
|
|
7020
|
+
: `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
|
|
7021
|
+
: "";
|
|
7022
|
+
const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
|
|
7023
|
+
function loadPersistedPaymentIntent() {
|
|
7024
|
+
if (typeof window === "undefined" || !storageKey)
|
|
7025
|
+
return null;
|
|
7026
|
+
try {
|
|
7027
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
7028
|
+
if (!raw)
|
|
7029
|
+
return null;
|
|
7030
|
+
const parsed = JSON.parse(raw);
|
|
7031
|
+
if (!parsed?.id || !parsed?.ts)
|
|
7032
|
+
return null;
|
|
7033
|
+
if (Date.now() - parsed.ts > PAYMENT_INTENT_TTL) {
|
|
7034
|
+
window.localStorage.removeItem(storageKey);
|
|
7035
|
+
return null;
|
|
7036
|
+
}
|
|
7037
|
+
return parsed.id;
|
|
7038
|
+
}
|
|
7039
|
+
catch {
|
|
7040
|
+
return null;
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
function persistPaymentIntent(id) {
|
|
7044
|
+
if (typeof window === "undefined" || !storageKey || !id)
|
|
7045
|
+
return;
|
|
7046
|
+
try {
|
|
7047
|
+
const payload = { id, ts: Date.now() };
|
|
7048
|
+
window.localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
7049
|
+
}
|
|
7050
|
+
catch { }
|
|
6902
7051
|
}
|
|
6903
7052
|
function clearPersistedPaymentIntent() {
|
|
6904
7053
|
if (typeof window === "undefined" || !storageKey)
|
|
@@ -6923,47 +7072,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6923
7072
|
}, [paymentIntentId]);
|
|
6924
7073
|
useEffect(() => {
|
|
6925
7074
|
const createStripePayment = async () => {
|
|
6926
|
-
if (!systemConfig || !eventDetails
|
|
7075
|
+
if (!systemConfig || !eventDetails) {
|
|
7076
|
+
return;
|
|
7077
|
+
}
|
|
7078
|
+
if (freeformSelection) {
|
|
7079
|
+
if (!freeformSelection.customerEmail?.trim() ||
|
|
7080
|
+
!freeformSelection.customerName?.trim() ||
|
|
7081
|
+
!freeformSelection.start ||
|
|
7082
|
+
!freeformSelection.end) {
|
|
7083
|
+
return;
|
|
7084
|
+
}
|
|
7085
|
+
}
|
|
7086
|
+
else if (!formData.participants?.length) {
|
|
6927
7087
|
return;
|
|
6928
7088
|
}
|
|
6929
7089
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6930
|
-
if (
|
|
7090
|
+
if (!freeformSelection &&
|
|
7091
|
+
(participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
|
|
6931
7092
|
return;
|
|
6932
7093
|
}
|
|
6933
7094
|
setIsCreatingPaymentIntent(true);
|
|
6934
7095
|
setPaymentError(null);
|
|
6935
7096
|
try {
|
|
6936
|
-
const
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
7097
|
+
const endpoint = freeformSelection
|
|
7098
|
+
? "/booking/create-freeform-stripe-payment"
|
|
7099
|
+
: "/booking/create-stripe-payment";
|
|
7100
|
+
const requestData = freeformSelection
|
|
7101
|
+
? {
|
|
7102
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
7103
|
+
organizationId: config.organizationId,
|
|
7104
|
+
start: freeformSelection.start,
|
|
7105
|
+
end: freeformSelection.end,
|
|
7106
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
7107
|
+
amount: Math.round(totalAmount),
|
|
7108
|
+
currency: "eur",
|
|
7109
|
+
customerName: freeformSelection.customerName.trim(),
|
|
7110
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
7111
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
7112
|
+
comment: freeformSelection.comment?.trim(),
|
|
7113
|
+
locale,
|
|
7114
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7115
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7116
|
+
}
|
|
7117
|
+
: {
|
|
7118
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
7119
|
+
organizationId: config.organizationId,
|
|
7120
|
+
amount: Math.round(totalAmount),
|
|
7121
|
+
currency: "eur",
|
|
7122
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
7123
|
+
discountCode: discountCode?.code,
|
|
7124
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
7125
|
+
customerName: formData.customerName?.trim(),
|
|
7126
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
7127
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
7128
|
+
comment: formData.comment?.trim(),
|
|
7129
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7130
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7131
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
7132
|
+
};
|
|
6954
7133
|
if (!requestData.organizationId) {
|
|
6955
7134
|
throw new Error("Organization ID is required");
|
|
6956
7135
|
}
|
|
6957
|
-
if (
|
|
7136
|
+
if (requestData.amount === undefined || requestData.amount < 0) {
|
|
6958
7137
|
throw new Error("Valid amount is required");
|
|
6959
7138
|
}
|
|
6960
|
-
if (!requestData
|
|
7139
|
+
if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
|
|
7140
|
+
throw new Error("Event instance ID is required");
|
|
7141
|
+
}
|
|
7142
|
+
if (!freeformSelection &&
|
|
7143
|
+
"participants" in requestData &&
|
|
7144
|
+
(!requestData.participants || requestData.participants.length === 0)) {
|
|
6961
7145
|
throw new Error("At least one participant is required");
|
|
6962
7146
|
}
|
|
6963
7147
|
if (!requestData.customerEmail) {
|
|
6964
7148
|
throw new Error("Customer email is required");
|
|
6965
7149
|
}
|
|
6966
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
7150
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
|
|
6967
7151
|
method: "POST",
|
|
6968
7152
|
headers: createApiHeaders(config, locale),
|
|
6969
7153
|
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
@@ -7008,13 +7192,17 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7008
7192
|
giftCards,
|
|
7009
7193
|
config,
|
|
7010
7194
|
upsellSelections,
|
|
7195
|
+
freeformSelection,
|
|
7196
|
+
locale,
|
|
7011
7197
|
]);
|
|
7012
7198
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
7013
|
-
const isFullyCoveredByGiftCards =
|
|
7199
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
7200
|
+
? false
|
|
7201
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
7014
7202
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
7015
7203
|
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
7016
7204
|
}
|
|
7017
|
-
if (isCreatingPaymentIntent || !clientSecret) {
|
|
7205
|
+
if (isCreatingPaymentIntent || !clientSecret || !stripePromise) {
|
|
7018
7206
|
return (jsxs("div", { style: {
|
|
7019
7207
|
display: "flex",
|
|
7020
7208
|
alignItems: "center",
|
|
@@ -7049,7 +7237,50 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7049
7237
|
setPaymentIntentId(null);
|
|
7050
7238
|
setClientSecret(null);
|
|
7051
7239
|
onSuccess(result);
|
|
7052
|
-
}, onError: onError }) }));
|
|
7240
|
+
}, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
|
|
7241
|
+
}
|
|
7242
|
+
|
|
7243
|
+
const HOLD_DURATION_SECONDS = 15 * 60;
|
|
7244
|
+
/**
|
|
7245
|
+
* Shows the remaining time on the 15-minute slot hold that the server places on
|
|
7246
|
+
* the pending order while the customer completes checkout. The timer is started
|
|
7247
|
+
* client-side when the payment step opens — matching the moment the pending
|
|
7248
|
+
* order (and its `holdExpiresAt`) is created server-side.
|
|
7249
|
+
*/
|
|
7250
|
+
function HoldCountdown({ onExpire }) {
|
|
7251
|
+
const t = useTranslations();
|
|
7252
|
+
const [remaining, setRemaining] = useState(HOLD_DURATION_SECONDS);
|
|
7253
|
+
useEffect(() => {
|
|
7254
|
+
const startedAt = Date.now();
|
|
7255
|
+
const interval = setInterval(() => {
|
|
7256
|
+
const elapsed = Math.floor((Date.now() - startedAt) / 1000);
|
|
7257
|
+
const left = Math.max(0, HOLD_DURATION_SECONDS - elapsed);
|
|
7258
|
+
setRemaining(left);
|
|
7259
|
+
if (left <= 0) {
|
|
7260
|
+
clearInterval(interval);
|
|
7261
|
+
onExpire?.();
|
|
7262
|
+
}
|
|
7263
|
+
}, 1000);
|
|
7264
|
+
return () => clearInterval(interval);
|
|
7265
|
+
}, [onExpire]);
|
|
7266
|
+
const minutes = Math.floor(remaining / 60);
|
|
7267
|
+
const seconds = remaining % 60;
|
|
7268
|
+
const formatted = `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
7269
|
+
const isLow = remaining <= 60;
|
|
7270
|
+
return (jsxs("div", { style: {
|
|
7271
|
+
display: "flex",
|
|
7272
|
+
alignItems: "center",
|
|
7273
|
+
gap: "8px",
|
|
7274
|
+
padding: "10px 14px",
|
|
7275
|
+
borderRadius: "var(--bw-border-radius)",
|
|
7276
|
+
border: `1px solid ${isLow ? "var(--bw-error-color)" : "var(--bw-border-color)"}`,
|
|
7277
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
7278
|
+
color: isLow ? "var(--bw-error-color)" : "var(--bw-text-color)",
|
|
7279
|
+
fontFamily: "var(--bw-font-family)",
|
|
7280
|
+
fontSize: "14px",
|
|
7281
|
+
}, children: [jsx(IconClock, {}), jsx("span", { children: remaining > 0
|
|
7282
|
+
? t("hold.reservedFor", { time: formatted })
|
|
7283
|
+
: t("hold.expired") })] }));
|
|
7053
7284
|
}
|
|
7054
7285
|
|
|
7055
7286
|
const FLAG_EMOJIS = {
|
|
@@ -11463,6 +11694,11 @@ const buttonStyles = {
|
|
|
11463
11694
|
color: "var(--bw-text-muted)",
|
|
11464
11695
|
border: "1px solid var(--bw-border-color)",
|
|
11465
11696
|
},
|
|
11697
|
+
// Size variants
|
|
11698
|
+
small: {
|
|
11699
|
+
padding: "8px 12px",
|
|
11700
|
+
fontSize: "13px",
|
|
11701
|
+
},
|
|
11466
11702
|
// Full width modifier
|
|
11467
11703
|
fullWidth: {
|
|
11468
11704
|
width: "100%",
|
|
@@ -11535,6 +11771,12 @@ const sectionStyles = {
|
|
|
11535
11771
|
// TEXT
|
|
11536
11772
|
// ============================================
|
|
11537
11773
|
const textStyles = {
|
|
11774
|
+
body: {
|
|
11775
|
+
fontSize: "14px",
|
|
11776
|
+
color: "var(--bw-text-color)",
|
|
11777
|
+
fontFamily: "var(--bw-font-family)",
|
|
11778
|
+
lineHeight: 1.5,
|
|
11779
|
+
},
|
|
11538
11780
|
muted: {
|
|
11539
11781
|
fontSize: "14px",
|
|
11540
11782
|
color: "var(--bw-text-muted)",
|
|
@@ -11604,7 +11846,7 @@ const sectionHeaderStyles$1 = sectionStyles.header;
|
|
|
11604
11846
|
const labelStyles$1 = formStyles.label;
|
|
11605
11847
|
const inputStyles$1 = formStyles.input;
|
|
11606
11848
|
const errorTextStyles$1 = formStyles.error;
|
|
11607
|
-
function BookingForm({ config, eventDetails,
|
|
11849
|
+
function BookingForm({ config, eventDetails, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
|
|
11608
11850
|
const t$1 = useTranslations();
|
|
11609
11851
|
const { locale } = useLocale();
|
|
11610
11852
|
const timezone = useTimezone();
|
|
@@ -11804,7 +12046,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11804
12046
|
newTotal: appliedDiscountCode.newTotal,
|
|
11805
12047
|
}
|
|
11806
12048
|
: null;
|
|
11807
|
-
const hasPaymentProvider =
|
|
12049
|
+
const hasPaymentProvider = systemConfig?.paymentProvider === "stripe" || systemConfig?.paymentProvider === "mollie";
|
|
11808
12050
|
const handleVoucherValidated = useCallback((voucher, _error) => {
|
|
11809
12051
|
if (voucher) {
|
|
11810
12052
|
setAppliedVouchers((prev) => [...prev, voucher]);
|
|
@@ -12070,7 +12312,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
12070
12312
|
...inputStyles$1,
|
|
12071
12313
|
resize: "vertical",
|
|
12072
12314
|
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: {
|
|
12315
|
+
} })] })] })] })), 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
12316
|
color: "var(--bw-text-color)",
|
|
12075
12317
|
fontWeight: 500,
|
|
12076
12318
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -12174,7 +12416,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
12174
12416
|
fontFamily: "var(--bw-font-family)",
|
|
12175
12417
|
marginTop: "8px",
|
|
12176
12418
|
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,
|
|
12419
|
+
}, 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
12420
|
}
|
|
12179
12421
|
|
|
12180
12422
|
/**
|
|
@@ -13255,20 +13497,22 @@ const sectionHeaderStyles = sectionStyles.header;
|
|
|
13255
13497
|
const labelStyles = formStyles.label;
|
|
13256
13498
|
const inputStyles = formStyles.input;
|
|
13257
13499
|
const errorTextStyles = formStyles.error;
|
|
13258
|
-
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13500
|
+
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13259
13501
|
const t = useTranslations();
|
|
13260
13502
|
const { locale } = useLocale();
|
|
13261
13503
|
const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
|
|
13262
13504
|
// Form state
|
|
13263
|
-
// Show voucher type selection if both monetary and event vouchers are allowed
|
|
13264
|
-
// The event type selection will handle the case when no event types are available
|
|
13265
13505
|
const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
|
|
13266
13506
|
const canUseEventVoucherType = voucherConfig.allowEventVouchers;
|
|
13267
|
-
const
|
|
13507
|
+
const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
|
|
13508
|
+
const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
|
|
13509
|
+
const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
|
|
13268
13510
|
const [voucherType, setVoucherType] = useState(canUseMonetaryVoucherType ? "monetary" : "event");
|
|
13269
13511
|
const [monetaryAmount, setMonetaryAmount] = useState(voucherConfig.monetaryPresets[2] || 10000);
|
|
13270
13512
|
const [selectedEventTypeId, setSelectedEventTypeId] = useState(null);
|
|
13271
13513
|
const [eventQuantity, setEventQuantity] = useState(1);
|
|
13514
|
+
const [selectedCategoryId, setSelectedCategoryId] = useState(null);
|
|
13515
|
+
const [categoryAmount, setCategoryAmount] = useState(10000);
|
|
13272
13516
|
const [recipientName, setRecipientName] = useState("");
|
|
13273
13517
|
const [message, setMessage] = useState("");
|
|
13274
13518
|
const [customerName, setCustomerName] = useState("");
|
|
@@ -13283,6 +13527,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13283
13527
|
const [stripeAppearance, setStripeAppearance] = useState(null);
|
|
13284
13528
|
// Initialize Stripe
|
|
13285
13529
|
useEffect(() => {
|
|
13530
|
+
if (!isOpen)
|
|
13531
|
+
return;
|
|
13286
13532
|
if (paymentProvider !== "stripe")
|
|
13287
13533
|
return;
|
|
13288
13534
|
if (systemConfig?.stripePublishableKey && !stripePromise) {
|
|
@@ -13294,7 +13540,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13294
13540
|
}
|
|
13295
13541
|
setStripePromise(loadStripe(systemConfig.stripePublishableKey, stripeOptions));
|
|
13296
13542
|
}
|
|
13297
|
-
}, [paymentProvider, systemConfig, locale, stripePromise]);
|
|
13543
|
+
}, [isOpen, paymentProvider, systemConfig, locale, stripePromise]);
|
|
13298
13544
|
// Initialize Stripe appearance
|
|
13299
13545
|
useEffect(() => {
|
|
13300
13546
|
const container = document.querySelector(".booking-widget-container");
|
|
@@ -13326,6 +13572,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13326
13572
|
if (voucherType === "monetary") {
|
|
13327
13573
|
return monetaryAmount;
|
|
13328
13574
|
}
|
|
13575
|
+
else if (voucherType === "category") {
|
|
13576
|
+
return categoryAmount;
|
|
13577
|
+
}
|
|
13329
13578
|
else {
|
|
13330
13579
|
const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13331
13580
|
if (eventType) {
|
|
@@ -13333,7 +13582,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13333
13582
|
}
|
|
13334
13583
|
return 0;
|
|
13335
13584
|
}
|
|
13336
|
-
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
|
|
13585
|
+
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
|
|
13337
13586
|
const selectedEventType = useMemo(() => {
|
|
13338
13587
|
return eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13339
13588
|
}, [eventTypes, selectedEventTypeId]);
|
|
@@ -13342,6 +13591,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13342
13591
|
voucherType,
|
|
13343
13592
|
selectedEventTypeId,
|
|
13344
13593
|
eventQuantity,
|
|
13594
|
+
selectedCategoryId,
|
|
13595
|
+
categoryAmount,
|
|
13345
13596
|
recipientName: recipientName.trim(),
|
|
13346
13597
|
message: message.trim(),
|
|
13347
13598
|
customerName: customerName.trim(),
|
|
@@ -13351,6 +13602,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13351
13602
|
voucherType,
|
|
13352
13603
|
selectedEventTypeId,
|
|
13353
13604
|
eventQuantity,
|
|
13605
|
+
selectedCategoryId,
|
|
13606
|
+
categoryAmount,
|
|
13354
13607
|
recipientName,
|
|
13355
13608
|
message,
|
|
13356
13609
|
customerName,
|
|
@@ -13369,24 +13622,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13369
13622
|
return false;
|
|
13370
13623
|
if (voucherType === "event" && !selectedEventTypeId)
|
|
13371
13624
|
return false;
|
|
13625
|
+
if (voucherType === "category" && !selectedCategoryId)
|
|
13626
|
+
return false;
|
|
13372
13627
|
return true;
|
|
13373
|
-
}, [customerName, customerEmail, acceptTerms, totalAmount,
|
|
13628
|
+
}, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
|
|
13374
13629
|
const defaultMonetaryAmount = useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
|
|
13375
13630
|
const handleVoucherTypeChange = useCallback((nextType) => {
|
|
13376
13631
|
setVoucherType(nextType);
|
|
13377
13632
|
setPaymentError(null);
|
|
13378
13633
|
if (nextType === "event") {
|
|
13379
|
-
// Keep voucher type mutually exclusive: entering event flow resets amount selection state.
|
|
13380
13634
|
setMonetaryAmount(defaultMonetaryAmount);
|
|
13635
|
+
setSelectedCategoryId(null);
|
|
13636
|
+
setCategoryAmount(10000);
|
|
13381
13637
|
if (eventTypes.length === 1) {
|
|
13382
13638
|
setSelectedEventTypeId(eventTypes[0]?.id ?? null);
|
|
13383
13639
|
}
|
|
13384
13640
|
return;
|
|
13385
13641
|
}
|
|
13386
|
-
|
|
13642
|
+
if (nextType === "category") {
|
|
13643
|
+
setMonetaryAmount(defaultMonetaryAmount);
|
|
13644
|
+
setSelectedEventTypeId(null);
|
|
13645
|
+
setEventQuantity(1);
|
|
13646
|
+
if (categories.length === 1) {
|
|
13647
|
+
setSelectedCategoryId(categories[0]?.id ?? null);
|
|
13648
|
+
}
|
|
13649
|
+
return;
|
|
13650
|
+
}
|
|
13387
13651
|
setSelectedEventTypeId(null);
|
|
13388
13652
|
setEventQuantity(1);
|
|
13389
|
-
|
|
13653
|
+
setSelectedCategoryId(null);
|
|
13654
|
+
setCategoryAmount(10000);
|
|
13655
|
+
}, [defaultMonetaryAmount, eventTypes, categories]);
|
|
13390
13656
|
useEffect(() => {
|
|
13391
13657
|
if (!canUseMonetaryVoucherType && voucherType === "monetary") {
|
|
13392
13658
|
setVoucherType("event");
|
|
@@ -13427,9 +13693,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13427
13693
|
organizationId: config.organizationId,
|
|
13428
13694
|
amount: totalAmount,
|
|
13429
13695
|
currency: "eur",
|
|
13430
|
-
voucherType,
|
|
13431
|
-
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13696
|
+
voucherType: voucherType === "category" ? "monetary" : voucherType,
|
|
13697
|
+
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13698
|
+
: voucherType === "category" ? categoryAmount
|
|
13699
|
+
: undefined,
|
|
13432
13700
|
eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
|
|
13701
|
+
eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
|
|
13433
13702
|
eventAmount: voucherType === "event" ? eventQuantity : undefined,
|
|
13434
13703
|
recipientName: recipientName.trim() || undefined,
|
|
13435
13704
|
message: message.trim() || undefined,
|
|
@@ -13620,7 +13889,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13620
13889
|
fontSize: "13px",
|
|
13621
13890
|
color: "var(--bw-text-muted)",
|
|
13622
13891
|
marginTop: "4px",
|
|
13623
|
-
}, children: t("voucher.eventTypeDesc") })] }))
|
|
13892
|
+
}, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
|
|
13893
|
+
flex: 1,
|
|
13894
|
+
padding: "16px",
|
|
13895
|
+
borderRadius: "var(--bw-border-radius)",
|
|
13896
|
+
border: voucherType === "category"
|
|
13897
|
+
? "2px solid var(--bw-highlight-color)"
|
|
13898
|
+
: "1px solid var(--bw-border-color)",
|
|
13899
|
+
backgroundColor: voucherType === "category"
|
|
13900
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
13901
|
+
: "var(--bw-surface-color)",
|
|
13902
|
+
cursor: "pointer",
|
|
13903
|
+
fontFamily: "var(--bw-font-family)",
|
|
13904
|
+
transition: "all 0.2s ease",
|
|
13905
|
+
textAlign: "center",
|
|
13906
|
+
}, children: [jsx("div", { style: {
|
|
13907
|
+
fontWeight: 600,
|
|
13908
|
+
color: voucherType === "category"
|
|
13909
|
+
? "var(--bw-highlight-color)"
|
|
13910
|
+
: "var(--bw-text-color)",
|
|
13911
|
+
}, children: t("voucher.categoryType") }), jsx("div", { style: {
|
|
13912
|
+
fontSize: "13px",
|
|
13913
|
+
color: "var(--bw-text-muted)",
|
|
13914
|
+
marginTop: "4px",
|
|
13915
|
+
}, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsx("div", { style: {
|
|
13624
13916
|
display: "grid",
|
|
13625
13917
|
gridTemplateColumns: "repeat(3, 1fr)",
|
|
13626
13918
|
gap: "8px",
|
|
@@ -13675,7 +13967,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13675
13967
|
fontSize: "18px",
|
|
13676
13968
|
color: "var(--bw-highlight-color)",
|
|
13677
13969
|
fontFamily: "var(--bw-font-family)",
|
|
13678
|
-
}, children: formatCurrency(totalAmount) })] }) }))] })), jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.
|
|
13970
|
+
}, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), jsx("div", { style: { marginTop: "12px" }, children: jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxs("div", { children: [jsx("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), jsxs("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
|
|
13971
|
+
...inputStyles,
|
|
13972
|
+
cursor: categories.length === 1 ? "not-allowed" : "pointer",
|
|
13973
|
+
}, children: [categories.length > 1 && (jsx("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (jsx("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (jsxs("div", { children: [jsx("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), jsx("div", { style: {
|
|
13974
|
+
display: "grid",
|
|
13975
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
13976
|
+
gap: "8px",
|
|
13977
|
+
marginTop: "4px",
|
|
13978
|
+
}, children: voucherConfig.monetaryPresets.map((preset) => (jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
|
|
13979
|
+
padding: "10px",
|
|
13980
|
+
borderRadius: "var(--bw-border-radius)",
|
|
13981
|
+
border: categoryAmount === preset
|
|
13982
|
+
? "2px solid var(--bw-highlight-color)"
|
|
13983
|
+
: "1px solid var(--bw-border-color)",
|
|
13984
|
+
backgroundColor: categoryAmount === preset
|
|
13985
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
13986
|
+
: "var(--bw-surface-color)",
|
|
13987
|
+
cursor: "pointer",
|
|
13988
|
+
fontFamily: "var(--bw-font-family)",
|
|
13989
|
+
fontWeight: 600,
|
|
13990
|
+
fontSize: "14px",
|
|
13991
|
+
color: categoryAmount === preset
|
|
13992
|
+
? "var(--bw-highlight-color)"
|
|
13993
|
+
: "var(--bw-text-color)",
|
|
13994
|
+
transition: "all 0.2s ease",
|
|
13995
|
+
}, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsx("div", { style: {
|
|
13996
|
+
marginTop: "16px",
|
|
13997
|
+
padding: "12px",
|
|
13998
|
+
backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
|
|
13999
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14000
|
+
border: "1px solid var(--bw-highlight-color)",
|
|
14001
|
+
}, children: jsxs("div", { style: {
|
|
14002
|
+
display: "flex",
|
|
14003
|
+
justifyContent: "space-between",
|
|
14004
|
+
alignItems: "center",
|
|
14005
|
+
}, children: [jsx("span", { style: {
|
|
14006
|
+
color: "var(--bw-text-color)",
|
|
14007
|
+
fontFamily: "var(--bw-font-family)",
|
|
14008
|
+
}, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsx("span", { style: {
|
|
14009
|
+
fontWeight: 700,
|
|
14010
|
+
fontSize: "18px",
|
|
14011
|
+
color: "var(--bw-highlight-color)",
|
|
14012
|
+
fontFamily: "var(--bw-font-family)",
|
|
14013
|
+
}, children: formatCurrency(categoryAmount) })] }) }))] })), jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsx("p", { style: {
|
|
13679
14014
|
fontSize: "13px",
|
|
13680
14015
|
color: "var(--bw-text-muted)",
|
|
13681
14016
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -13708,7 +14043,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13708
14043
|
fontFamily: "var(--bw-font-family)",
|
|
13709
14044
|
}, children: voucherType === "monetary"
|
|
13710
14045
|
? t("voucher.monetaryVoucher")
|
|
13711
|
-
:
|
|
14046
|
+
: voucherType === "category"
|
|
14047
|
+
? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
|
|
14048
|
+
: `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxs("div", { style: {
|
|
13712
14049
|
fontSize: "13px",
|
|
13713
14050
|
color: "var(--bw-text-muted)",
|
|
13714
14051
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -13984,7 +14321,7 @@ function VoucherAttachment({ onClick }) {
|
|
|
13984
14321
|
}, children: t("voucher.buyAsGift") })] }));
|
|
13985
14322
|
}
|
|
13986
14323
|
|
|
13987
|
-
function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14324
|
+
function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
13988
14325
|
if (!voucherConfig?.enabled) {
|
|
13989
14326
|
return null;
|
|
13990
14327
|
}
|
|
@@ -13995,7 +14332,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
|
|
|
13995
14332
|
justifyContent: "center",
|
|
13996
14333
|
}, children: jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
|
|
13997
14334
|
? Math.min(...eventTypes.map((et) => et.maxPrice))
|
|
13998
|
-
: undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
|
|
14335
|
+
: undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), 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 && (jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
|
|
13999
14336
|
}
|
|
14000
14337
|
|
|
14001
14338
|
// Helper function to preprocess markdown for underline support
|
|
@@ -14258,8 +14595,17 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14258
14595
|
if (!item)
|
|
14259
14596
|
return jsx("div", { style: { height: "34px" } }, i);
|
|
14260
14597
|
const hasDiscount = item.basePrice > 0 && item.price < item.basePrice;
|
|
14261
|
-
|
|
14262
|
-
|
|
14598
|
+
const isRowBookable = item.bookingOpen && item.availableSpots > 0;
|
|
14599
|
+
return (jsxs("div", { onClick: (e) => {
|
|
14600
|
+
e.stopPropagation();
|
|
14601
|
+
if (isRowBookable) {
|
|
14602
|
+
onInstancePreview?.(item.id, eventType.id);
|
|
14603
|
+
}
|
|
14604
|
+
}, onMouseEnter: (e) => {
|
|
14605
|
+
if (onInstancePreview && isRowBookable) {
|
|
14606
|
+
e.currentTarget.style.backgroundColor = "var(--bw-highlight-subtle)";
|
|
14607
|
+
}
|
|
14608
|
+
}, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
|
|
14263
14609
|
height: "34px",
|
|
14264
14610
|
display: "grid",
|
|
14265
14611
|
gridTemplateColumns: "auto 1fr auto 20px",
|
|
@@ -14268,12 +14614,13 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14268
14614
|
width: "100%",
|
|
14269
14615
|
fontSize: "13px",
|
|
14270
14616
|
color: "var(--bw-text-muted)",
|
|
14271
|
-
cursor: onInstancePreview ? "pointer" : "default",
|
|
14617
|
+
cursor: onInstancePreview && isRowBookable ? "pointer" : "default",
|
|
14272
14618
|
borderRadius: "4px",
|
|
14273
14619
|
padding: "0 2px",
|
|
14274
14620
|
transition: "background 0.15s",
|
|
14275
14621
|
boxSizing: "border-box",
|
|
14276
|
-
|
|
14622
|
+
opacity: isRowBookable ? 1 : 0.55,
|
|
14623
|
+
}, 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
14624
|
}) }), hasMoreDates && (jsx("button", { type: "button", onClick: (e) => {
|
|
14278
14625
|
e.stopPropagation();
|
|
14279
14626
|
onEventTypeSelect(eventType);
|
|
@@ -14362,6 +14709,74 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14362
14709
|
}) }) })), jsx(PaymentLogosStrip, {}), jsx(EventTypeDetailsDialog, { isOpen: detailsDialogOpen, onClose: handleCloseDetails, eventType: selectedEventTypeForDetails, onEventTypeSelect: onEventTypeSelect })] }));
|
|
14363
14710
|
}
|
|
14364
14711
|
|
|
14712
|
+
function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName, onClose, }) {
|
|
14713
|
+
const t = useTranslations();
|
|
14714
|
+
const [name, setName] = useState("");
|
|
14715
|
+
const [email, setEmail] = useState("");
|
|
14716
|
+
const [phone, setPhone] = useState("");
|
|
14717
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
14718
|
+
const [error, setError] = useState(null);
|
|
14719
|
+
const [position, setPosition] = useState(null);
|
|
14720
|
+
const handleSubmit = async () => {
|
|
14721
|
+
setError(null);
|
|
14722
|
+
if (!name.trim() || !email.trim()) {
|
|
14723
|
+
setError(t("waitlist.requiredFields"));
|
|
14724
|
+
return;
|
|
14725
|
+
}
|
|
14726
|
+
setIsSubmitting(true);
|
|
14727
|
+
try {
|
|
14728
|
+
const response = await fetch(getApiUrl(apiBaseUrl, "/booking/waitlist"), {
|
|
14729
|
+
method: "POST",
|
|
14730
|
+
headers: { "Content-Type": "application/json" },
|
|
14731
|
+
body: JSON.stringify({
|
|
14732
|
+
organizationId,
|
|
14733
|
+
eventInstanceId,
|
|
14734
|
+
name: name.trim(),
|
|
14735
|
+
email: email.trim(),
|
|
14736
|
+
phone: phone.trim() || undefined,
|
|
14737
|
+
}),
|
|
14738
|
+
});
|
|
14739
|
+
if (!response.ok) {
|
|
14740
|
+
throw new Error("request_failed");
|
|
14741
|
+
}
|
|
14742
|
+
const data = (await response.json());
|
|
14743
|
+
setPosition(data.position ?? null);
|
|
14744
|
+
}
|
|
14745
|
+
catch {
|
|
14746
|
+
setError(t("waitlist.submitError"));
|
|
14747
|
+
}
|
|
14748
|
+
finally {
|
|
14749
|
+
setIsSubmitting(false);
|
|
14750
|
+
}
|
|
14751
|
+
};
|
|
14752
|
+
return (jsx("div", { style: {
|
|
14753
|
+
position: "fixed",
|
|
14754
|
+
inset: 0,
|
|
14755
|
+
zIndex: 1000,
|
|
14756
|
+
display: "flex",
|
|
14757
|
+
alignItems: "center",
|
|
14758
|
+
justifyContent: "center",
|
|
14759
|
+
backgroundColor: "rgba(15, 23, 42, 0.6)",
|
|
14760
|
+
padding: "16px",
|
|
14761
|
+
fontFamily: "var(--bw-font-family)",
|
|
14762
|
+
}, onClick: onClose, children: jsx("div", { style: {
|
|
14763
|
+
width: "100%",
|
|
14764
|
+
maxWidth: "420px",
|
|
14765
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
14766
|
+
border: "1px solid var(--bw-border-color)",
|
|
14767
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14768
|
+
padding: "24px",
|
|
14769
|
+
}, onClick: (e) => e.stopPropagation(), children: position !== null ? (jsxs("div", { style: { textAlign: "center" }, children: [jsx("h3", { style: {
|
|
14770
|
+
margin: "0 0 8px",
|
|
14771
|
+
color: "var(--bw-highlight-color)",
|
|
14772
|
+
fontSize: "18px",
|
|
14773
|
+
}, 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: {
|
|
14774
|
+
margin: "0 0 4px",
|
|
14775
|
+
color: "var(--bw-highlight-color)",
|
|
14776
|
+
fontSize: "18px",
|
|
14777
|
+
}, 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") })] })] })) }) }));
|
|
14778
|
+
}
|
|
14779
|
+
|
|
14365
14780
|
const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
|
|
14366
14781
|
const availabilityRatio = availableSpots / maxParticipants;
|
|
14367
14782
|
if (availabilityRatio >= 0.6)
|
|
@@ -14486,12 +14901,13 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
|
|
|
14486
14901
|
boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
|
|
14487
14902
|
}, children: formatCurrency(price) }));
|
|
14488
14903
|
};
|
|
14489
|
-
function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, }) {
|
|
14904
|
+
function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
|
|
14490
14905
|
const t = useTranslations();
|
|
14491
14906
|
const { locale } = useLocale();
|
|
14492
14907
|
const timezone = useTimezone();
|
|
14493
14908
|
const [selectedEventInstanceId, setSelectedEventInstanceId] = useState(null);
|
|
14494
14909
|
const [openGroups, setOpenGroups] = useState(new Set());
|
|
14910
|
+
const [waitlistEvent, setWaitlistEvent] = useState(null);
|
|
14495
14911
|
const getMonthPriceDisplayInfo = (minPrice) => {
|
|
14496
14912
|
return getPriceDisplayInfo(minPrice, yearPrices, t);
|
|
14497
14913
|
};
|
|
@@ -14652,116 +15068,127 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
|
|
|
14652
15068
|
fontFamily: "var(--bw-font-family)",
|
|
14653
15069
|
}, children: t("instances.noAvailable") }), jsx("p", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("instances.noAvailableMessage") })] }) }) }));
|
|
14654
15070
|
}
|
|
14655
|
-
return (
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14670
|
-
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
15071
|
+
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) => {
|
|
15072
|
+
const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
|
|
15073
|
+
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: {
|
|
15074
|
+
fontSize: "16px",
|
|
15075
|
+
fontWeight: 500,
|
|
15076
|
+
marginLeft: "auto",
|
|
15077
|
+
padding: "4px 8px",
|
|
15078
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
15079
|
+
backgroundColor: monthPriceDisplayInfo
|
|
15080
|
+
? monthPriceDisplayInfo.backgroundColor
|
|
15081
|
+
: "#14532d",
|
|
15082
|
+
color: monthPriceDisplayInfo
|
|
15083
|
+
? monthPriceDisplayInfo.textColor
|
|
15084
|
+
: "#4ade80",
|
|
15085
|
+
boxShadow: monthPriceDisplayInfo
|
|
15086
|
+
? "0 2px 4px rgba(0, 0, 0, 0.2)"
|
|
15087
|
+
: undefined,
|
|
15088
|
+
}, 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) => {
|
|
15089
|
+
const availableSpots = event.maxParticipants - event.participantCount;
|
|
15090
|
+
const isFullyBooked = availableSpots === 0;
|
|
15091
|
+
const startDate = new Date(event.startTime);
|
|
15092
|
+
const isPastEvent = today.toISOString() >= startDate.toISOString();
|
|
15093
|
+
const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
|
|
15094
|
+
const canWaitlist = isFullyBooked &&
|
|
15095
|
+
!isPastEvent &&
|
|
15096
|
+
!!apiBaseUrl &&
|
|
15097
|
+
!!organizationId;
|
|
15098
|
+
return (jsxs("div", { style: {
|
|
15099
|
+
position: "relative",
|
|
15100
|
+
border: "1px solid var(--bw-border-color)",
|
|
15101
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
15102
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15103
|
+
padding: "16px 10px",
|
|
15104
|
+
transition: "all 0.2s ease",
|
|
15105
|
+
fontFamily: "var(--bw-font-family)",
|
|
15106
|
+
opacity: isDisabled && !canWaitlist ? 0.3 : 1,
|
|
15107
|
+
filter: isDisabled && !canWaitlist ? "grayscale(40%)" : "none",
|
|
15108
|
+
cursor: isDisabled
|
|
15109
|
+
? canWaitlist
|
|
15110
|
+
? "default"
|
|
15111
|
+
: "not-allowed"
|
|
15112
|
+
: "pointer",
|
|
15113
|
+
}, onClick: () => {
|
|
15114
|
+
if (!isDisabled) {
|
|
15115
|
+
handleEventInstanceSelect(event);
|
|
15116
|
+
}
|
|
15117
|
+
}, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
|
|
15118
|
+
position: "absolute",
|
|
15119
|
+
inset: 0,
|
|
15120
|
+
backgroundColor: "rgba(15, 23, 42, 0.8)",
|
|
15121
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15122
|
+
display: "flex",
|
|
15123
|
+
alignItems: "center",
|
|
15124
|
+
justifyContent: "center",
|
|
15125
|
+
}, children: jsx("div", { style: {
|
|
15126
|
+
width: "32px",
|
|
15127
|
+
height: "32px",
|
|
15128
|
+
color: "var(--bw-highlight-color)",
|
|
15129
|
+
opacity: 0.8,
|
|
15130
|
+
fontSize: "32px",
|
|
15131
|
+
}, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
|
|
15132
|
+
display: "flex",
|
|
15133
|
+
justifyContent: "space-between",
|
|
15134
|
+
width: "100%",
|
|
15135
|
+
alignItems: "flex-start",
|
|
15136
|
+
gap: "12px",
|
|
15137
|
+
marginBottom: "4px",
|
|
15138
|
+
}, children: [jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsx("div", { style: {
|
|
15139
|
+
fontSize: "16px",
|
|
15140
|
+
transition: "all 0.2s ease",
|
|
15141
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
15142
|
+
borderTop: "4px solid var(--bw-border-color)",
|
|
15143
|
+
border: "1px solid var(--bw-border-color)",
|
|
15144
|
+
width: "40px",
|
|
15145
|
+
height: "40px",
|
|
15146
|
+
display: "flex",
|
|
15147
|
+
alignItems: "center",
|
|
15148
|
+
justifyContent: "center",
|
|
15149
|
+
fontWeight: 700,
|
|
15150
|
+
color: "var(--bw-text-color)",
|
|
15151
|
+
backgroundColor: "var(--bw-background-color)",
|
|
15152
|
+
}, children: startDate.getDate() }), jsxs("div", { style: {
|
|
15153
|
+
fontSize: "16px",
|
|
15154
|
+
color: "var(--bw-text-color)",
|
|
15155
|
+
display: "flex",
|
|
15156
|
+
flexDirection: "column",
|
|
15157
|
+
alignItems: "flex-start",
|
|
15158
|
+
justifyContent: "flex-start",
|
|
15159
|
+
lineHeight: 1.25,
|
|
15160
|
+
}, children: [jsxs("div", { children: [jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
|
|
15161
|
+
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) ===
|
|
15162
|
+
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: {
|
|
15163
|
+
fontSize: "12px",
|
|
15164
|
+
fontWeight: 400,
|
|
15165
|
+
color: "var(--bw-text-muted)",
|
|
15166
|
+
marginLeft: "6px",
|
|
15167
|
+
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
15168
|
+
whiteSpace: "nowrap",
|
|
15169
|
+
padding: "2px 6px",
|
|
15170
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
15171
|
+
}, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsx("div", { style: {
|
|
15172
|
+
textAlign: "right",
|
|
15173
|
+
display: "flex",
|
|
15174
|
+
flexDirection: "column",
|
|
15175
|
+
alignItems: "flex-end",
|
|
15176
|
+
}, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { style: {
|
|
15177
|
+
fontSize: "16px",
|
|
15178
|
+
fontWeight: 600,
|
|
15179
|
+
color: "var(--bw-text-color)",
|
|
15180
|
+
lineHeight: 1.25,
|
|
15181
|
+
margin: "0 0 2px 0",
|
|
15182
|
+
display: "flex",
|
|
15183
|
+
alignItems: "center",
|
|
15184
|
+
gap: "8px",
|
|
15185
|
+
maxWidth: "230px",
|
|
15186
|
+
}, children: event.name })), canWaitlist && (jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth, buttonStyles.small, { marginTop: "8px" }), onClick: (e) => {
|
|
15187
|
+
e.stopPropagation();
|
|
15188
|
+
setWaitlistEvent(event);
|
|
15189
|
+
}, children: t("waitlist.joinCta") }))] }, event.id));
|
|
15190
|
+
}) }) })] }, key));
|
|
15191
|
+
}) })] }), waitlistEvent && apiBaseUrl && organizationId && (jsx(WaitlistDialog, { apiBaseUrl: apiBaseUrl, organizationId: organizationId, eventInstanceId: waitlistEvent.id, eventName: waitlistEvent.name, onClose: () => setWaitlistEvent(null) }))] }));
|
|
14765
15192
|
}
|
|
14766
15193
|
|
|
14767
15194
|
function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText, showAllButton, isLoadingEventDetails = false, isLoadingShowAll = false, isLoading = false, count = 3, }) {
|
|
@@ -15173,6 +15600,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
|
|
|
15173
15600
|
}) })] }));
|
|
15174
15601
|
}
|
|
15175
15602
|
|
|
15603
|
+
function toMinutesForUnit(unit) {
|
|
15604
|
+
switch (unit.toLowerCase()) {
|
|
15605
|
+
case "minute":
|
|
15606
|
+
case "minutes":
|
|
15607
|
+
return 1;
|
|
15608
|
+
case "hour":
|
|
15609
|
+
case "hours":
|
|
15610
|
+
return 60;
|
|
15611
|
+
case "day":
|
|
15612
|
+
case "days":
|
|
15613
|
+
return 24 * 60;
|
|
15614
|
+
default:
|
|
15615
|
+
return 1;
|
|
15616
|
+
}
|
|
15617
|
+
}
|
|
15618
|
+
// --- Timezone helpers ------------------------------------------------------
|
|
15619
|
+
// The booking is evaluated server-side against the organization's local
|
|
15620
|
+
// operating hours. The customer's browser may be in a different timezone, so we
|
|
15621
|
+
// interpret the datetime-local field as a wall-clock time in the ORG timezone
|
|
15622
|
+
// (not the browser's) and convert to a UTC instant for the API.
|
|
15623
|
+
function getTzParts(date, timeZone) {
|
|
15624
|
+
const dtf = new Intl.DateTimeFormat("en-CA", {
|
|
15625
|
+
timeZone,
|
|
15626
|
+
hour12: false,
|
|
15627
|
+
year: "numeric",
|
|
15628
|
+
month: "2-digit",
|
|
15629
|
+
day: "2-digit",
|
|
15630
|
+
hour: "2-digit",
|
|
15631
|
+
minute: "2-digit",
|
|
15632
|
+
second: "2-digit",
|
|
15633
|
+
});
|
|
15634
|
+
const parts = {};
|
|
15635
|
+
for (const part of dtf.formatToParts(date)) {
|
|
15636
|
+
if (part.type !== "literal")
|
|
15637
|
+
parts[part.type] = part.value;
|
|
15638
|
+
}
|
|
15639
|
+
return parts;
|
|
15640
|
+
}
|
|
15641
|
+
function tzOffsetMs(utcMs, timeZone) {
|
|
15642
|
+
const p = getTzParts(new Date(utcMs), timeZone);
|
|
15643
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15644
|
+
const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
|
|
15645
|
+
return asLocalUtc - utcMs;
|
|
15646
|
+
}
|
|
15647
|
+
/** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
|
|
15648
|
+
function zonedWallTimeToUtc(wallTime, timeZone) {
|
|
15649
|
+
const [datePart, timePart] = wallTime.split("T");
|
|
15650
|
+
if (!datePart || !timePart)
|
|
15651
|
+
return new Date(NaN);
|
|
15652
|
+
const [y, mo, d] = datePart.split("-").map(Number);
|
|
15653
|
+
const [h, mi] = timePart.split(":").map(Number);
|
|
15654
|
+
const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
|
|
15655
|
+
// Single-pass offset correction (good enough outside the ~1h DST fold).
|
|
15656
|
+
const offset = tzOffsetMs(naiveUtc, timeZone);
|
|
15657
|
+
return new Date(naiveUtc - offset);
|
|
15658
|
+
}
|
|
15659
|
+
/** Render a UTC instant as an org-tz datetime-local input value. */
|
|
15660
|
+
function utcToZonedInputValue(date, timeZone) {
|
|
15661
|
+
const p = getTzParts(date, timeZone);
|
|
15662
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15663
|
+
return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
|
|
15664
|
+
}
|
|
15665
|
+
/** Format a UTC instant for display in the org timezone. */
|
|
15666
|
+
function formatInTimeZone(date, timeZone, locale) {
|
|
15667
|
+
return new Intl.DateTimeFormat(locale, {
|
|
15668
|
+
timeZone,
|
|
15669
|
+
dateStyle: "medium",
|
|
15670
|
+
timeStyle: "short",
|
|
15671
|
+
}).format(date);
|
|
15672
|
+
}
|
|
15673
|
+
function calculateTieredPrice(durationUnits, config) {
|
|
15674
|
+
const tiers = [...(config.tiers ?? [])]
|
|
15675
|
+
.filter((tier) => tier.unitCount > 0 && tier.price >= 0)
|
|
15676
|
+
.sort((a, b) => b.unitCount - a.unitCount);
|
|
15677
|
+
if (tiers.length === 0) {
|
|
15678
|
+
return 0;
|
|
15679
|
+
}
|
|
15680
|
+
let total = 0;
|
|
15681
|
+
let remaining = durationUnits;
|
|
15682
|
+
for (const tier of tiers) {
|
|
15683
|
+
const count = Math.floor(remaining / tier.unitCount);
|
|
15684
|
+
if (count <= 0)
|
|
15685
|
+
continue;
|
|
15686
|
+
total += count * tier.price;
|
|
15687
|
+
remaining -= count * tier.unitCount;
|
|
15688
|
+
}
|
|
15689
|
+
if (remaining > 0) {
|
|
15690
|
+
if (typeof config.perUnitPrice === "number") {
|
|
15691
|
+
total += remaining * config.perUnitPrice;
|
|
15692
|
+
}
|
|
15693
|
+
else {
|
|
15694
|
+
total += tiers[tiers.length - 1]?.price ?? 0;
|
|
15695
|
+
}
|
|
15696
|
+
}
|
|
15697
|
+
return total;
|
|
15698
|
+
}
|
|
15699
|
+
function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
|
|
15700
|
+
const t = useTranslations();
|
|
15701
|
+
const { locale } = useLocale();
|
|
15702
|
+
const freeformConfig = eventType.freeformConfig;
|
|
15703
|
+
const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
15704
|
+
const isNamedResource = eventType.resourceKind === "named";
|
|
15705
|
+
const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
|
|
15706
|
+
const now = useMemo(() => {
|
|
15707
|
+
const next = new Date();
|
|
15708
|
+
next.setMinutes(next.getMinutes() + 30, 0, 0);
|
|
15709
|
+
return next;
|
|
15710
|
+
}, []);
|
|
15711
|
+
const [startInput, setStartInput] = useState(utcToZonedInputValue(now, timeZone));
|
|
15712
|
+
const [duration, setDuration] = useState(Math.max(1, freeformConfig?.minDuration ?? 1));
|
|
15713
|
+
const [requestedUnits, setRequestedUnits] = useState(1);
|
|
15714
|
+
const [customerName, setCustomerName] = useState("");
|
|
15715
|
+
const [customerEmail, setCustomerEmail] = useState("");
|
|
15716
|
+
const [customerPhone, setCustomerPhone] = useState("");
|
|
15717
|
+
const [comment, setComment] = useState("");
|
|
15718
|
+
const [acceptTerms, setAcceptTerms] = useState(false);
|
|
15719
|
+
const [checkoutStep, setCheckoutStep] = useState("details");
|
|
15720
|
+
const [availability, setAvailability] = useState(null);
|
|
15721
|
+
const [availabilityError, setAvailabilityError] = useState(null);
|
|
15722
|
+
const [isCheckingAvailability, setIsCheckingAvailability] = useState(false);
|
|
15723
|
+
useEffect(() => {
|
|
15724
|
+
if (!isOpen)
|
|
15725
|
+
return;
|
|
15726
|
+
setCheckoutStep("details");
|
|
15727
|
+
}, [isOpen]);
|
|
15728
|
+
useEffect(() => {
|
|
15729
|
+
if (!freeformConfig)
|
|
15730
|
+
return;
|
|
15731
|
+
setDuration(Math.max(1, freeformConfig.minDuration));
|
|
15732
|
+
setRequestedUnits(1);
|
|
15733
|
+
setAvailability(null);
|
|
15734
|
+
setAvailabilityError(null);
|
|
15735
|
+
}, [freeformConfig?.resourceId]);
|
|
15736
|
+
const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
|
|
15737
|
+
const startDate = useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
|
|
15738
|
+
const endDate = useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
|
|
15739
|
+
useEffect(() => {
|
|
15740
|
+
if (!isOpen || !freeformConfig)
|
|
15741
|
+
return;
|
|
15742
|
+
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
|
|
15743
|
+
return;
|
|
15744
|
+
const timer = setTimeout(async () => {
|
|
15745
|
+
setIsCheckingAvailability(true);
|
|
15746
|
+
setAvailabilityError(null);
|
|
15747
|
+
try {
|
|
15748
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
|
|
15749
|
+
method: "POST",
|
|
15750
|
+
headers: createApiHeaders(config, locale),
|
|
15751
|
+
body: JSON.stringify(createRequestBody(config, {
|
|
15752
|
+
eventTypeId: eventType.id,
|
|
15753
|
+
requestedUnits,
|
|
15754
|
+
start: startDate.toISOString(),
|
|
15755
|
+
end: endDate.toISOString(),
|
|
15756
|
+
})),
|
|
15757
|
+
});
|
|
15758
|
+
const data = await response.json();
|
|
15759
|
+
if (!response.ok) {
|
|
15760
|
+
setAvailability(null);
|
|
15761
|
+
setAvailabilityError(data.error || t("error.loadBookingData"));
|
|
15762
|
+
return;
|
|
15763
|
+
}
|
|
15764
|
+
setAvailability({
|
|
15765
|
+
available: Boolean(data.available),
|
|
15766
|
+
freeUnits: Number(data.freeUnits ?? 0),
|
|
15767
|
+
capacityUnits: Number(data.capacityUnits ?? 0),
|
|
15768
|
+
usedUnits: Number(data.usedUnits ?? 0),
|
|
15769
|
+
reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
|
|
15770
|
+
blockUsedUnits: Number(data.blockUsedUnits ?? 0),
|
|
15771
|
+
durationUnits: Number(data.durationUnits ?? 0),
|
|
15772
|
+
roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
|
|
15773
|
+
});
|
|
15774
|
+
}
|
|
15775
|
+
catch (_error) {
|
|
15776
|
+
setAvailability(null);
|
|
15777
|
+
setAvailabilityError(t("error.loadBookingData"));
|
|
15778
|
+
}
|
|
15779
|
+
finally {
|
|
15780
|
+
setIsCheckingAvailability(false);
|
|
15781
|
+
}
|
|
15782
|
+
}, 350);
|
|
15783
|
+
return () => clearTimeout(timer);
|
|
15784
|
+
}, [
|
|
15785
|
+
config,
|
|
15786
|
+
endDate,
|
|
15787
|
+
eventType.id,
|
|
15788
|
+
freeformConfig,
|
|
15789
|
+
isOpen,
|
|
15790
|
+
locale,
|
|
15791
|
+
requestedUnits,
|
|
15792
|
+
startDate,
|
|
15793
|
+
t,
|
|
15794
|
+
]);
|
|
15795
|
+
const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
|
|
15796
|
+
const basePrice = useMemo(() => {
|
|
15797
|
+
if (!freeformConfig)
|
|
15798
|
+
return 0;
|
|
15799
|
+
if (freeformConfig.pricingModel === "perUnit") {
|
|
15800
|
+
return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
|
|
15801
|
+
}
|
|
15802
|
+
return calculateTieredPrice(billedDurationUnits, freeformConfig);
|
|
15803
|
+
}, [billedDurationUnits, freeformConfig]);
|
|
15804
|
+
const totalPrice = Math.max(0, basePrice * requestedUnits);
|
|
15805
|
+
const canContinueToPayment = !!freeformConfig &&
|
|
15806
|
+
customerName.trim().length >= 2 &&
|
|
15807
|
+
customerEmail.trim().length > 3 &&
|
|
15808
|
+
acceptTerms &&
|
|
15809
|
+
!!availability?.available &&
|
|
15810
|
+
!isCheckingAvailability;
|
|
15811
|
+
const freeformSelection = useMemo(() => ({
|
|
15812
|
+
eventTypeId: eventType.id,
|
|
15813
|
+
start: startDate.toISOString(),
|
|
15814
|
+
end: endDate.toISOString(),
|
|
15815
|
+
requestedUnits,
|
|
15816
|
+
customerName: customerName.trim(),
|
|
15817
|
+
customerEmail: customerEmail.trim(),
|
|
15818
|
+
customerPhone: customerPhone.trim(),
|
|
15819
|
+
comment: comment.trim(),
|
|
15820
|
+
}), [
|
|
15821
|
+
comment,
|
|
15822
|
+
customerEmail,
|
|
15823
|
+
customerName,
|
|
15824
|
+
customerPhone,
|
|
15825
|
+
endDate,
|
|
15826
|
+
eventType.id,
|
|
15827
|
+
requestedUnits,
|
|
15828
|
+
startDate,
|
|
15829
|
+
]);
|
|
15830
|
+
const paymentFormData = useMemo(() => ({
|
|
15831
|
+
customerName: customerName.trim(),
|
|
15832
|
+
customerEmail: customerEmail.trim(),
|
|
15833
|
+
customerPhone: customerPhone.trim(),
|
|
15834
|
+
participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
|
|
15835
|
+
comment: comment.trim(),
|
|
15836
|
+
}), [comment, customerEmail, customerName, customerPhone]);
|
|
15837
|
+
const eventDetailsForPayment = useMemo(() => ({
|
|
15838
|
+
id: eventType.id,
|
|
15839
|
+
name: eventType.name,
|
|
15840
|
+
startTime: startDate.toISOString(),
|
|
15841
|
+
endTime: endDate.toISOString(),
|
|
15842
|
+
price: basePrice,
|
|
15843
|
+
maxParticipants: availability?.capacityUnits ?? requestedUnits,
|
|
15844
|
+
participantCount: 0,
|
|
15845
|
+
availableSpots: availability?.freeUnits ?? requestedUnits,
|
|
15846
|
+
durationDays: 1,
|
|
15847
|
+
durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
|
|
15848
|
+
images: eventType.images ?? [],
|
|
15849
|
+
category: eventType.category,
|
|
15850
|
+
organization: {
|
|
15851
|
+
id: config.organizationId,
|
|
15852
|
+
name: "",
|
|
15853
|
+
},
|
|
15854
|
+
bookingOpen: availability?.available ?? false,
|
|
15855
|
+
}), [
|
|
15856
|
+
availability?.available,
|
|
15857
|
+
availability?.capacityUnits,
|
|
15858
|
+
availability?.freeUnits,
|
|
15859
|
+
availability?.roundedDurationMinutes,
|
|
15860
|
+
basePrice,
|
|
15861
|
+
config.organizationId,
|
|
15862
|
+
duration,
|
|
15863
|
+
endDate,
|
|
15864
|
+
eventType.category,
|
|
15865
|
+
eventType.id,
|
|
15866
|
+
eventType.images,
|
|
15867
|
+
eventType.name,
|
|
15868
|
+
requestedUnits,
|
|
15869
|
+
startDate,
|
|
15870
|
+
unitMinutes,
|
|
15871
|
+
]);
|
|
15872
|
+
const footer = (jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsx("button", { type: "button", onClick: () => {
|
|
15873
|
+
if (checkoutStep === "payment") {
|
|
15874
|
+
setCheckoutStep("details");
|
|
15875
|
+
}
|
|
15876
|
+
else {
|
|
15877
|
+
onClose();
|
|
15878
|
+
}
|
|
15879
|
+
}, style: {
|
|
15880
|
+
flex: 1,
|
|
15881
|
+
padding: "12px 16px",
|
|
15882
|
+
border: "1px solid var(--bw-border-color)",
|
|
15883
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15884
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
15885
|
+
color: "var(--bw-text-color)",
|
|
15886
|
+
fontFamily: "var(--bw-font-family)",
|
|
15887
|
+
fontWeight: 600,
|
|
15888
|
+
cursor: "pointer",
|
|
15889
|
+
}, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
|
|
15890
|
+
flex: 1,
|
|
15891
|
+
padding: "12px 16px",
|
|
15892
|
+
border: "none",
|
|
15893
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15894
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
15895
|
+
color: "var(--bw-button-text-color, #ffffff)",
|
|
15896
|
+
fontFamily: "var(--bw-font-family)",
|
|
15897
|
+
fontWeight: 700,
|
|
15898
|
+
cursor: canContinueToPayment ? "pointer" : "not-allowed",
|
|
15899
|
+
opacity: canContinueToPayment ? 1 : 0.6,
|
|
15900
|
+
}, children: t("button.continueToPayment") }))] }));
|
|
15901
|
+
if (!freeformConfig) {
|
|
15902
|
+
return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: jsx("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
|
|
15903
|
+
}
|
|
15904
|
+
return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: jsx("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (jsxs(Fragment, { children: [jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), 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)" } })] }), jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), 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 && (jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), 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)" } })] }))] }), 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 && (jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (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
|
|
15905
|
+
? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
|
|
15906
|
+
: t("booking.freeformNotAvailable") }))] }), jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), jsxs("div", { style: { display: "grid", gap: "10px" }, children: [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)" } }), 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)" } }), 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)" } }), 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" } }), jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [jsx("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [jsx("span", { children: t("booking.duration") }), jsx("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [jsx("span", { children: t("booking.price") }), jsxs("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), 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: [jsx("span", { children: t("summary.totalAmount") }), jsx("span", { children: formatCurrency(totalPrice) })] })] })] })) : (jsxs(Fragment, { children: [jsx(HoldCountdown, {}), jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (jsx("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (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" && (jsx(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
|
|
15907
|
+
}
|
|
15908
|
+
|
|
15176
15909
|
const getThemeConfig = (theme = "generic") => {
|
|
15177
15910
|
switch (theme) {
|
|
15178
15911
|
case "christmas":
|
|
@@ -15655,6 +16388,7 @@ let buffer = [];
|
|
|
15655
16388
|
let flushTimer = null;
|
|
15656
16389
|
let currentApiBaseUrl = "";
|
|
15657
16390
|
let currentOrganizationId = "";
|
|
16391
|
+
let currentPartnerContractId = null;
|
|
15658
16392
|
const FLUSH_INTERVAL_MS = 3000;
|
|
15659
16393
|
function flush() {
|
|
15660
16394
|
if (buffer.length === 0)
|
|
@@ -15696,9 +16430,10 @@ function scheduleFlush() {
|
|
|
15696
16430
|
/**
|
|
15697
16431
|
* Initialise the analytics module. Must be called once before `trackEvent`.
|
|
15698
16432
|
*/
|
|
15699
|
-
function initAnalytics(apiBaseUrl, organizationId) {
|
|
16433
|
+
function initAnalytics(apiBaseUrl, organizationId, options) {
|
|
15700
16434
|
currentApiBaseUrl = apiBaseUrl;
|
|
15701
16435
|
currentOrganizationId = organizationId;
|
|
16436
|
+
currentPartnerContractId = options?.partnerContractId?.trim() || null;
|
|
15702
16437
|
if (typeof window !== "undefined") {
|
|
15703
16438
|
window.addEventListener("pagehide", flush);
|
|
15704
16439
|
window.addEventListener("visibilitychange", () => {
|
|
@@ -15715,9 +16450,12 @@ function trackEvent(event, properties = {}) {
|
|
|
15715
16450
|
return;
|
|
15716
16451
|
if (!currentOrganizationId)
|
|
15717
16452
|
return;
|
|
16453
|
+
const mergedProperties = currentPartnerContractId
|
|
16454
|
+
? { ...properties, partnerContractId: currentPartnerContractId }
|
|
16455
|
+
: properties;
|
|
15718
16456
|
buffer.push({
|
|
15719
16457
|
event,
|
|
15720
|
-
properties,
|
|
16458
|
+
properties: mergedProperties,
|
|
15721
16459
|
domain: window.location.hostname,
|
|
15722
16460
|
url: window.location.href,
|
|
15723
16461
|
referrer: document.referrer,
|
|
@@ -15793,7 +16531,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15793
16531
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
15794
16532
|
// Booking flow state
|
|
15795
16533
|
const [eventDetails, setEventDetails] = useState(null);
|
|
15796
|
-
const [stripePromise, setStripePromise] = useState(null);
|
|
15797
16534
|
// SEPARATED LOADING STATES
|
|
15798
16535
|
const [isLoading, setIsLoading] = useState(true);
|
|
15799
16536
|
const [isLoadingEventInstances, setIsLoadingEventInstances] = useState(false);
|
|
@@ -15809,6 +16546,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15809
16546
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
15810
16547
|
const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
|
|
15811
16548
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
|
|
16549
|
+
const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = useState(false);
|
|
15812
16550
|
// Google Ads config (received from API, set once from the first API response)
|
|
15813
16551
|
const [googleAdsConfig, setGoogleAdsConfig] = useState(null);
|
|
15814
16552
|
const extractGoogleAdsConfig = (data) => {
|
|
@@ -15822,6 +16560,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15822
16560
|
// Voucher purchase state
|
|
15823
16561
|
const [voucherConfig, setVoucherConfig] = useState(null);
|
|
15824
16562
|
const [voucherEventTypes, setVoucherEventTypes] = useState([]);
|
|
16563
|
+
const [voucherCategories, setVoucherCategories] = useState([]);
|
|
15825
16564
|
const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = useState(false);
|
|
15826
16565
|
const [isVoucherFormOpen, setIsVoucherFormOpen] = useState(false);
|
|
15827
16566
|
const [voucherPurchaseResult, setVoucherPurchaseResult] = useState(null);
|
|
@@ -15901,6 +16640,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15901
16640
|
setVoucherConfig(mergedConfig);
|
|
15902
16641
|
extractGoogleAdsConfig(data);
|
|
15903
16642
|
setVoucherEventTypes(data.eventTypes || []);
|
|
16643
|
+
setVoucherCategories(data.categories || []);
|
|
15904
16644
|
// Set system config for payment processing
|
|
15905
16645
|
if (data.paymentProvider) {
|
|
15906
16646
|
setSystemConfig({
|
|
@@ -15928,7 +16668,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15928
16668
|
useEffect(() => {
|
|
15929
16669
|
if (!analyticsInitRef.current && config.organizationId) {
|
|
15930
16670
|
analyticsInitRef.current = true;
|
|
15931
|
-
initAnalytics(config.apiBaseUrl, config.organizationId
|
|
16671
|
+
initAnalytics(config.apiBaseUrl, config.organizationId, {
|
|
16672
|
+
...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
|
|
16673
|
+
});
|
|
15932
16674
|
trackEvent("widget_loaded", {
|
|
15933
16675
|
viewMode,
|
|
15934
16676
|
eventTypeId: config.eventTypeId,
|
|
@@ -15937,7 +16679,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15937
16679
|
isStandaloneVoucherMode,
|
|
15938
16680
|
});
|
|
15939
16681
|
}
|
|
15940
|
-
}, [config.organizationId, config.apiBaseUrl]);
|
|
16682
|
+
}, [config.organizationId, config.apiBaseUrl, config.partnerContractId, viewMode, config.eventTypeId, config.categoryId, config.eventInstanceId, isStandaloneVoucherMode]);
|
|
15941
16683
|
// Fire widget pageview once when Google Ads config is received from API
|
|
15942
16684
|
const pageviewFiredRef = useRef(false);
|
|
15943
16685
|
useEffect(() => {
|
|
@@ -15951,6 +16693,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15951
16693
|
const initializeWidget = async () => {
|
|
15952
16694
|
try {
|
|
15953
16695
|
setIsLoading(true);
|
|
16696
|
+
if (config.partnerContractId) {
|
|
16697
|
+
const validationResponse = await fetch(getApiUrl(config.apiBaseUrl, `/booking/validate-partner?organizationId=${encodeURIComponent(config.organizationId)}&contractId=${config.partnerContractId}`), {
|
|
16698
|
+
method: "GET",
|
|
16699
|
+
headers: createApiHeaders(config, locale),
|
|
16700
|
+
});
|
|
16701
|
+
if (!validationResponse.ok) {
|
|
16702
|
+
setError("Invalid partner configuration");
|
|
16703
|
+
config.onError?.("Invalid partner configuration");
|
|
16704
|
+
return;
|
|
16705
|
+
}
|
|
16706
|
+
const validationData = (await validationResponse.json());
|
|
16707
|
+
if (!validationData.valid) {
|
|
16708
|
+
setError("Invalid partner configuration");
|
|
16709
|
+
config.onError?.("Invalid partner configuration");
|
|
16710
|
+
return;
|
|
16711
|
+
}
|
|
16712
|
+
}
|
|
15954
16713
|
// Load voucher config in parallel if voucher mode is requested or there's event selection
|
|
15955
16714
|
if (isVoucherModeRequested || hasEventSelection) {
|
|
15956
16715
|
void loadVoucherConfig();
|
|
@@ -16086,11 +16845,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16086
16845
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
16087
16846
|
setIsSuccess(true);
|
|
16088
16847
|
setSuccessPaymentId(null);
|
|
16848
|
+
return;
|
|
16089
16849
|
}
|
|
16090
16850
|
}
|
|
16091
16851
|
catch {
|
|
16092
|
-
//
|
|
16093
|
-
}
|
|
16852
|
+
// Fall back to booking success flow if voucher lookup fails.
|
|
16853
|
+
}
|
|
16854
|
+
// Not a voucher purchase: treat as a booking return and open the
|
|
16855
|
+
// booking confirmation, mirroring the Stripe redirect handler.
|
|
16856
|
+
trackEvent("booking_completed", { paymentIntentId: mollieReturn.molliePaymentId, source: "mollie_redirect" });
|
|
16857
|
+
setVoucherPurchaseResult(null);
|
|
16858
|
+
setSuccessPaymentId(mollieReturn.molliePaymentId);
|
|
16859
|
+
setIsSuccess(true);
|
|
16094
16860
|
};
|
|
16095
16861
|
if (!isLoading) {
|
|
16096
16862
|
void handleMollieVoucherReturn();
|
|
@@ -16279,27 +17045,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16279
17045
|
widgetPaymentMode: data.widgetPaymentMode ?? "all",
|
|
16280
17046
|
});
|
|
16281
17047
|
}
|
|
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
17048
|
if (!skipInstanceAutoSelectRef.current && data.eventInstances.length === 1) {
|
|
16304
17049
|
setSelectedEventInstance(data.eventInstances[0]);
|
|
16305
17050
|
setCurrentStep("booking");
|
|
@@ -16339,29 +17084,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16339
17084
|
roundPricesEnabled: data.roundPricesEnabled ?? true,
|
|
16340
17085
|
widgetPaymentMode: data.widgetPaymentMode ?? "all",
|
|
16341
17086
|
});
|
|
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
17087
|
}
|
|
16366
17088
|
else {
|
|
16367
17089
|
const errorMessage = data.error || t("error.loadEventDetails");
|
|
@@ -16426,6 +17148,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16426
17148
|
const handleEventTypeSelect = async (eventType) => {
|
|
16427
17149
|
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16428
17150
|
setSelectedEventType(eventType);
|
|
17151
|
+
if (eventType.bookingMode === "freeform") {
|
|
17152
|
+
setCurrentStep("freeform");
|
|
17153
|
+
setShouldRenderFreeformSelection(true);
|
|
17154
|
+
setIsLoadingEventInstances(true);
|
|
17155
|
+
try {
|
|
17156
|
+
await loadEventInstances(eventType.id);
|
|
17157
|
+
}
|
|
17158
|
+
finally {
|
|
17159
|
+
setIsLoadingEventInstances(false);
|
|
17160
|
+
}
|
|
17161
|
+
return;
|
|
17162
|
+
}
|
|
16429
17163
|
setCurrentStep("eventInstances");
|
|
16430
17164
|
setShouldRenderInstanceSelection(true);
|
|
16431
17165
|
setIsLoadingEventInstances(true);
|
|
@@ -16516,6 +17250,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16516
17250
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16517
17251
|
setSidebarOpen(false);
|
|
16518
17252
|
setShouldRenderBookingForm(false);
|
|
17253
|
+
setShouldRenderFreeformSelection(false);
|
|
16519
17254
|
setBookingPersistedState(null);
|
|
16520
17255
|
config.onSuccess?.(result);
|
|
16521
17256
|
};
|
|
@@ -16630,12 +17365,12 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16630
17365
|
startTime: cardPreviewItem.startTime,
|
|
16631
17366
|
endTime: cardPreviewItem.startTime,
|
|
16632
17367
|
price: cardPreviewItem.price,
|
|
16633
|
-
maxParticipants: cardPreviewItem.availableSpots
|
|
16634
|
-
participantCount:
|
|
17368
|
+
maxParticipants: Math.max(cardPreviewItem.availableSpots, 0),
|
|
17369
|
+
participantCount: 0,
|
|
16635
17370
|
availableSpots: cardPreviewItem.availableSpots,
|
|
16636
17371
|
durationDays: 1,
|
|
16637
17372
|
durationPerDay: 1,
|
|
16638
|
-
bookingOpen:
|
|
17373
|
+
bookingOpen: cardPreviewItem.bookingOpen && cardPreviewItem.availableSpots > 0,
|
|
16639
17374
|
}
|
|
16640
17375
|
: null;
|
|
16641
17376
|
if (eventInstance) {
|
|
@@ -16823,7 +17558,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16823
17558
|
}
|
|
16824
17559
|
// Main view based on view mode
|
|
16825
17560
|
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,
|
|
17561
|
+
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
17562
|
setIsSuccess(false);
|
|
16828
17563
|
setCurrentStep("eventTypes");
|
|
16829
17564
|
setShowingPreview(true);
|
|
@@ -16831,6 +17566,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16831
17566
|
setShouldRenderInstanceSelection(false);
|
|
16832
17567
|
setShouldRenderUpsells(false);
|
|
16833
17568
|
setShouldRenderBookingForm(false);
|
|
17569
|
+
setShouldRenderFreeformSelection(false);
|
|
16834
17570
|
setSelectedUpsells([]);
|
|
16835
17571
|
setUpsells([]);
|
|
16836
17572
|
setBookingPersistedState(null);
|
|
@@ -16844,7 +17580,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16844
17580
|
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16845
17581
|
}
|
|
16846
17582
|
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,
|
|
17583
|
+
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
17584
|
setIsSuccess(false);
|
|
16849
17585
|
setCurrentStep("eventTypes");
|
|
16850
17586
|
setShowingPreview(true);
|
|
@@ -16852,6 +17588,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16852
17588
|
setShouldRenderInstanceSelection(false);
|
|
16853
17589
|
setShouldRenderUpsells(false);
|
|
16854
17590
|
setShouldRenderBookingForm(false);
|
|
17591
|
+
setShouldRenderFreeformSelection(false);
|
|
16855
17592
|
setSelectedUpsells([]);
|
|
16856
17593
|
setUpsells([]);
|
|
16857
17594
|
setBookingPersistedState(null);
|
|
@@ -16864,13 +17601,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16864
17601
|
}, isOpen: currentStep === "eventInstances", onClose: () => {
|
|
16865
17602
|
setShowingPreview(true);
|
|
16866
17603
|
setCurrentStep("eventTypes");
|
|
16867
|
-
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17604
|
+
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
16868
17605
|
setIsSuccess(false);
|
|
16869
17606
|
setCurrentStep("eventTypes");
|
|
16870
17607
|
setShowingPreview(true);
|
|
16871
17608
|
setSuccessPaymentId(null);
|
|
16872
17609
|
setShouldRenderInstanceSelection(false);
|
|
16873
17610
|
setShouldRenderBookingForm(false);
|
|
17611
|
+
setShouldRenderFreeformSelection(false);
|
|
16874
17612
|
const url = new URL(window.location.href);
|
|
16875
17613
|
url.searchParams.delete("payment_intent");
|
|
16876
17614
|
url.searchParams.delete("payment_intent_client_secret");
|
|
@@ -16914,13 +17652,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16914
17652
|
setCurrentStep("booking");
|
|
16915
17653
|
setShouldRenderBookingForm(true);
|
|
16916
17654
|
}
|
|
17655
|
+
else if (selectedEventType?.bookingMode === "freeform") {
|
|
17656
|
+
setCurrentStep("freeform");
|
|
17657
|
+
setShouldRenderFreeformSelection(true);
|
|
17658
|
+
}
|
|
16917
17659
|
else {
|
|
16918
17660
|
setCurrentStep("eventInstances");
|
|
16919
17661
|
setSidebarOpen(true);
|
|
16920
17662
|
setShouldRenderInstanceSelection(true);
|
|
16921
17663
|
}
|
|
16922
17664
|
}, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
|
|
16923
|
-
(isDirectInstanceMode
|
|
17665
|
+
(isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
|
|
17666
|
+
? t("button.bookNow")
|
|
17667
|
+
: 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 })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
|
|
17668
|
+
setCurrentStep("eventTypes");
|
|
17669
|
+
setSidebarOpen(false);
|
|
17670
|
+
setShouldRenderFreeformSelection(false);
|
|
17671
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
16924
17672
|
setIsSuccess(false);
|
|
16925
17673
|
setCurrentStep("eventTypes");
|
|
16926
17674
|
setSidebarOpen(false);
|
|
@@ -16928,6 +17676,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16928
17676
|
setShouldRenderInstanceSelection(false);
|
|
16929
17677
|
setShouldRenderUpsells(false);
|
|
16930
17678
|
setShouldRenderBookingForm(false);
|
|
17679
|
+
setShouldRenderFreeformSelection(false);
|
|
16931
17680
|
setSelectedUpsells([]);
|
|
16932
17681
|
setUpsells([]);
|
|
16933
17682
|
const url = new URL(window.location.href);
|
|
@@ -16940,7 +17689,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16940
17689
|
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16941
17690
|
}
|
|
16942
17691
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
16943
|
-
const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (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 && (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 && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
|
|
17692
|
+
const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (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 && (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 && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
|
|
16944
17693
|
display: "inline-block",
|
|
16945
17694
|
width: "32px",
|
|
16946
17695
|
height: "32px",
|
|
@@ -16981,13 +17730,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16981
17730
|
};
|
|
16982
17731
|
};
|
|
16983
17732
|
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,
|
|
17733
|
+
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 })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
|
|
17734
|
+
setCurrentStep("eventTypes");
|
|
17735
|
+
setShouldRenderFreeformSelection(false);
|
|
17736
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
|
|
16985
17737
|
setIsSuccess(false);
|
|
16986
17738
|
setCurrentStep("eventTypes");
|
|
16987
17739
|
setSuccessPaymentId(null);
|
|
16988
17740
|
setShouldRenderInstanceSelection(false);
|
|
16989
17741
|
setShouldRenderUpsells(false);
|
|
16990
17742
|
setShouldRenderBookingForm(false);
|
|
17743
|
+
setShouldRenderFreeformSelection(false);
|
|
16991
17744
|
setSelectedUpsells([]);
|
|
16992
17745
|
setUpsells([]);
|
|
16993
17746
|
const url = new URL(window.location.href);
|
|
@@ -16997,7 +17750,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16997
17750
|
url.searchParams.delete("mollie_payment_id");
|
|
16998
17751
|
url.searchParams.delete("mollie_status");
|
|
16999
17752
|
window.history.replaceState({}, "", url.toString());
|
|
17000
|
-
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), 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: () => {
|
|
17753
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), 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: () => {
|
|
17001
17754
|
setIsSuccess(false);
|
|
17002
17755
|
setVoucherPurchaseResult(null);
|
|
17003
17756
|
const url = new URL(window.location.href);
|
|
@@ -17045,7 +17798,7 @@ function styleInject(css, ref) {
|
|
|
17045
17798
|
}
|
|
17046
17799
|
}
|
|
17047
17800
|
|
|
17048
|
-
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}}";
|
|
17801
|
+
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}}";
|
|
17049
17802
|
styleInject(css_248z);
|
|
17050
17803
|
|
|
17051
17804
|
// Export init function for vanilla JS usage
|