@bigz-app/booking-widget 1.4.5 → 1.5.2
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 +710 -112
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts +21 -105
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- 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 -1
- 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/EventTypeSelection.d.ts +17 -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/NextEventsPreview.d.ts +1 -1
- package/dist/components/events/NextEventsPreview.d.ts.map +1 -1
- package/dist/components/events/SpecialsView.d.ts +1 -1
- package/dist/components/events/SpecialsView.d.ts.map +1 -1
- package/dist/components/events/index.d.ts +1 -0
- package/dist/components/events/index.d.ts.map +1 -1
- package/dist/components/events/types.d.ts +42 -0
- package/dist/components/events/types.d.ts.map +1 -0
- package/dist/components/upsells/UpsellCard.d.ts +1 -1
- package/dist/components/upsells/UpsellCard.d.ts.map +1 -1
- package/dist/components/upsells/UpsellsStep.d.ts +2 -44
- package/dist/components/upsells/UpsellsStep.d.ts.map +1 -1
- package/dist/components/upsells/types.d.ts +44 -0
- package/dist/components/upsells/types.d.ts.map +1 -0
- 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 +9 -16
- 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/types.d.ts +15 -0
- package/dist/components/voucher/types.d.ts.map +1 -0
- package/dist/components/voucher/useVoucherConfig.d.ts +6 -0
- package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
- package/dist/config.d.ts +66 -0
- package/dist/config.d.ts.map +1 -0
- 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 +710 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +710 -112
- package/dist/index.esm.js.map +1 -1
- package/dist/styles/StyleProvider.d.ts +1 -1
- package/dist/styles/StyleProvider.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -149,6 +149,10 @@ const de$1 = {
|
|
|
149
149
|
"booking.stepUpsells": "Extras",
|
|
150
150
|
"booking.stepDetails": "Angaben",
|
|
151
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.",
|
|
152
156
|
// Booking summary
|
|
153
157
|
"summary.title": "Buchungszusammenfassung",
|
|
154
158
|
"summary.subtotal": "Zwischensumme:",
|
|
@@ -204,6 +208,13 @@ const de$1 = {
|
|
|
204
208
|
"voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
|
|
205
209
|
"voucher.eventType": "Erlebnisgutschein",
|
|
206
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",
|
|
207
218
|
"voucher.selectAmount": "Betrag wählen",
|
|
208
219
|
"voucher.customAmount": "Oder eigenen Betrag eingeben",
|
|
209
220
|
"voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
|
|
@@ -457,6 +468,10 @@ const en = {
|
|
|
457
468
|
"booking.stepUpsells": "Extras",
|
|
458
469
|
"booking.stepDetails": "Details",
|
|
459
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.",
|
|
460
475
|
// Booking summary
|
|
461
476
|
"summary.title": "Booking Summary",
|
|
462
477
|
"summary.subtotal": "Subtotal:",
|
|
@@ -512,6 +527,13 @@ const en = {
|
|
|
512
527
|
"voucher.monetaryTypeDesc": "Choose any amount",
|
|
513
528
|
"voucher.eventType": "Event Voucher",
|
|
514
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",
|
|
515
537
|
"voucher.selectAmount": "Select Amount",
|
|
516
538
|
"voucher.customAmount": "Or enter custom amount",
|
|
517
539
|
"voucher.amountRange": "Amount must be between {{min}} and {{max}}",
|
|
@@ -699,6 +721,21 @@ const es = {
|
|
|
699
721
|
"instances.bestPrice": "¡¡¡mejor precio!!!",
|
|
700
722
|
"instances.goodPrice": "buen precio",
|
|
701
723
|
"instances.oclock": "",
|
|
724
|
+
// Lista de espera
|
|
725
|
+
"waitlist.joinCta": "Lista de espera",
|
|
726
|
+
"waitlist.title": "Apuntarse a la lista de espera",
|
|
727
|
+
"waitlist.subtitle": "{{name}} está agotado. Deja tus datos y te contactaremos si se libera un lugar.",
|
|
728
|
+
"waitlist.name": "Nombre",
|
|
729
|
+
"waitlist.email": "Correo electrónico",
|
|
730
|
+
"waitlist.phone": "Teléfono (opcional)",
|
|
731
|
+
"waitlist.requiredFields": "Por favor, introduce tu nombre y correo electrónico.",
|
|
732
|
+
"waitlist.join": "Apuntarse",
|
|
733
|
+
"waitlist.submitting": "Apuntando...",
|
|
734
|
+
"waitlist.cancel": "Cancelar",
|
|
735
|
+
"waitlist.submitError": "Algo salió mal. Por favor, inténtalo de nuevo.",
|
|
736
|
+
"waitlist.successTitle": "¡Estás en la lista!",
|
|
737
|
+
"waitlist.successMessage": "Eres el número {{position}} en la lista de espera. Te contactaremos si se libera un lugar.",
|
|
738
|
+
"waitlist.done": "Listo",
|
|
702
739
|
// Next events preview
|
|
703
740
|
"nextEvents.title": "Próximas fechas disponibles",
|
|
704
741
|
"nextEvents.subtitle": "Selecciona una fecha o consulta todas las fechas disponibles",
|
|
@@ -802,6 +839,13 @@ const es = {
|
|
|
802
839
|
"voucher.monetaryTypeDesc": "Elige cualquier cantidad",
|
|
803
840
|
"voucher.eventType": "Vale de experiencia",
|
|
804
841
|
"voucher.eventTypeDesc": "Para una experiencia específica",
|
|
842
|
+
"voucher.categoryType": "Vale de categoría",
|
|
843
|
+
"voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
|
|
844
|
+
"voucher.categoryVoucher": "Vale de categoría",
|
|
845
|
+
"voucher.selectCategory": "Seleccionar categoría",
|
|
846
|
+
"voucher.categoryLabel": "Categoría",
|
|
847
|
+
"voucher.selectCategoryPlaceholder": "Elige una categoría...",
|
|
848
|
+
"voucher.categoryAmount": "Valor del vale",
|
|
805
849
|
"voucher.selectAmount": "Selecciona el importe",
|
|
806
850
|
"voucher.customAmount": "O introduce un importe personalizado",
|
|
807
851
|
"voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
|
|
@@ -989,6 +1033,21 @@ const pt = {
|
|
|
989
1033
|
"instances.bestPrice": "melhor preço !!!",
|
|
990
1034
|
"instances.goodPrice": "bom preço",
|
|
991
1035
|
"instances.oclock": "",
|
|
1036
|
+
// Lista de espera
|
|
1037
|
+
"waitlist.joinCta": "Lista de espera",
|
|
1038
|
+
"waitlist.title": "Entrar na lista de espera",
|
|
1039
|
+
"waitlist.subtitle": "{{name}} está esgotado. Deixe os seus dados e entraremos em contacto se surgir uma vaga.",
|
|
1040
|
+
"waitlist.name": "Nome",
|
|
1041
|
+
"waitlist.email": "E-mail",
|
|
1042
|
+
"waitlist.phone": "Telefone (opcional)",
|
|
1043
|
+
"waitlist.requiredFields": "Por favor, introduza o seu nome e e-mail.",
|
|
1044
|
+
"waitlist.join": "Inscrever-se",
|
|
1045
|
+
"waitlist.submitting": "A inscrever...",
|
|
1046
|
+
"waitlist.cancel": "Cancelar",
|
|
1047
|
+
"waitlist.submitError": "Algo correu mal. Por favor, tente novamente.",
|
|
1048
|
+
"waitlist.successTitle": "Está na lista!",
|
|
1049
|
+
"waitlist.successMessage": "É o número {{position}} na lista de espera. Entraremos em contacto se surgir uma vaga.",
|
|
1050
|
+
"waitlist.done": "Concluído",
|
|
992
1051
|
// Next events preview
|
|
993
1052
|
"nextEvents.title": "Próximas datas disponíveis",
|
|
994
1053
|
"nextEvents.subtitle": "Selecione uma data ou veja todas as datas disponíveis",
|
|
@@ -1092,6 +1151,13 @@ const pt = {
|
|
|
1092
1151
|
"voucher.monetaryTypeDesc": "Escolha qualquer quantia",
|
|
1093
1152
|
"voucher.eventType": "Vale de experiência",
|
|
1094
1153
|
"voucher.eventTypeDesc": "Para uma experiência específica",
|
|
1154
|
+
"voucher.categoryType": "Vale de categoria",
|
|
1155
|
+
"voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
|
|
1156
|
+
"voucher.categoryVoucher": "Vale de categoria",
|
|
1157
|
+
"voucher.selectCategory": "Selecionar categoria",
|
|
1158
|
+
"voucher.categoryLabel": "Categoria",
|
|
1159
|
+
"voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
|
|
1160
|
+
"voucher.categoryAmount": "Valor do vale",
|
|
1095
1161
|
"voucher.selectAmount": "Selecione o valor",
|
|
1096
1162
|
"voucher.customAmount": "Ou introduza um valor personalizado",
|
|
1097
1163
|
"voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
|
|
@@ -1279,6 +1345,21 @@ const sv = {
|
|
|
1279
1345
|
"instances.bestPrice": "bästa pris !!!",
|
|
1280
1346
|
"instances.goodPrice": "bra pris",
|
|
1281
1347
|
"instances.oclock": "",
|
|
1348
|
+
// Väntelista
|
|
1349
|
+
"waitlist.joinCta": "Ställ dig i kö",
|
|
1350
|
+
"waitlist.title": "Ställ dig i kö",
|
|
1351
|
+
"waitlist.subtitle": "{{name}} är fullbokat. Lämna dina uppgifter så kontaktar vi dig om en plats blir ledig.",
|
|
1352
|
+
"waitlist.name": "Namn",
|
|
1353
|
+
"waitlist.email": "E-post",
|
|
1354
|
+
"waitlist.phone": "Telefon (valfritt)",
|
|
1355
|
+
"waitlist.requiredFields": "Ange ditt namn och din e-postadress.",
|
|
1356
|
+
"waitlist.join": "Ställ dig i kö",
|
|
1357
|
+
"waitlist.submitting": "Anmäler...",
|
|
1358
|
+
"waitlist.cancel": "Avbryt",
|
|
1359
|
+
"waitlist.submitError": "Något gick fel. Försök igen.",
|
|
1360
|
+
"waitlist.successTitle": "Du står i kön!",
|
|
1361
|
+
"waitlist.successMessage": "Du är nummer {{position}} på väntelistan. Vi hör av oss om en plats blir ledig.",
|
|
1362
|
+
"waitlist.done": "Klar",
|
|
1282
1363
|
// Next events preview
|
|
1283
1364
|
"nextEvents.title": "Nästa tillgängliga datum",
|
|
1284
1365
|
"nextEvents.subtitle": "Välj ett datum eller visa alla tillgängliga datum",
|
|
@@ -1382,6 +1463,13 @@ const sv = {
|
|
|
1382
1463
|
"voucher.monetaryTypeDesc": "Välj valfritt belopp",
|
|
1383
1464
|
"voucher.eventType": "Upplevelsebevis",
|
|
1384
1465
|
"voucher.eventTypeDesc": "För en specifik upplevelse",
|
|
1466
|
+
"voucher.categoryType": "Kategoripresent",
|
|
1467
|
+
"voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
|
|
1468
|
+
"voucher.categoryVoucher": "Kategoripresent",
|
|
1469
|
+
"voucher.selectCategory": "Välj kategori",
|
|
1470
|
+
"voucher.categoryLabel": "Kategori",
|
|
1471
|
+
"voucher.selectCategoryPlaceholder": "Välj en kategori...",
|
|
1472
|
+
"voucher.categoryAmount": "Presentkortsvärde",
|
|
1385
1473
|
"voucher.selectAmount": "Välj belopp",
|
|
1386
1474
|
"voucher.customAmount": "Eller ange eget belopp",
|
|
1387
1475
|
"voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
|
|
@@ -6352,7 +6440,7 @@ function loadMollieScript() {
|
|
|
6352
6440
|
document.head.appendChild(script);
|
|
6353
6441
|
});
|
|
6354
6442
|
}
|
|
6355
|
-
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
|
|
6443
|
+
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
|
|
6356
6444
|
const t = useTranslations();
|
|
6357
6445
|
const { locale } = useLocale();
|
|
6358
6446
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -6366,7 +6454,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6366
6454
|
const cardFormRef = useRef(null);
|
|
6367
6455
|
const cardContainerRef = useRef(null);
|
|
6368
6456
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6369
|
-
const isFullyCoveredByGiftCards =
|
|
6457
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
6458
|
+
? false
|
|
6459
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
6370
6460
|
const isCreditCard = selectedMethod === "creditcard";
|
|
6371
6461
|
useEffect(() => {
|
|
6372
6462
|
if (isFullyCoveredByGiftCards)
|
|
@@ -6476,6 +6566,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6476
6566
|
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6477
6567
|
}
|
|
6478
6568
|
const validateBeforePayment = () => {
|
|
6569
|
+
if (freeformSelection) {
|
|
6570
|
+
return null;
|
|
6571
|
+
}
|
|
6479
6572
|
const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
|
|
6480
6573
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6481
6574
|
if (participantCount > availableSpots) {
|
|
@@ -6487,21 +6580,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6487
6580
|
return null;
|
|
6488
6581
|
};
|
|
6489
6582
|
const buildRequestData = (cardToken) => ({
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6583
|
+
...(freeformSelection
|
|
6584
|
+
? {
|
|
6585
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
6586
|
+
organizationId: config.organizationId,
|
|
6587
|
+
start: freeformSelection.start,
|
|
6588
|
+
end: freeformSelection.end,
|
|
6589
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
6590
|
+
amount: Math.round(totalAmount),
|
|
6591
|
+
currency: "EUR",
|
|
6592
|
+
customerName: freeformSelection.customerName.trim(),
|
|
6593
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
6594
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
6595
|
+
comment: freeformSelection.comment?.trim(),
|
|
6596
|
+
locale,
|
|
6597
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6598
|
+
...(cardToken && { cardToken }),
|
|
6599
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6600
|
+
}
|
|
6601
|
+
: {
|
|
6602
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
6603
|
+
organizationId: config.organizationId,
|
|
6604
|
+
amount: Math.round(totalAmount),
|
|
6605
|
+
currency: "EUR",
|
|
6606
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
6607
|
+
discountCode: discountCode?.code,
|
|
6608
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
6609
|
+
customerName: formData.customerName?.trim(),
|
|
6610
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
6611
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
6612
|
+
comment: formData.comment?.trim(),
|
|
6613
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6614
|
+
...(cardToken && { cardToken }),
|
|
6615
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6616
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
6617
|
+
}),
|
|
6505
6618
|
});
|
|
6506
6619
|
const handleCardPayment = async () => {
|
|
6507
6620
|
if (!mollieRef.current)
|
|
@@ -6519,7 +6632,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6519
6632
|
setPaymentError(tokenError?.message || t("error.createPayment"));
|
|
6520
6633
|
return;
|
|
6521
6634
|
}
|
|
6522
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6635
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6636
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6637
|
+
: "/booking/create-mollie-payment"), {
|
|
6523
6638
|
method: "POST",
|
|
6524
6639
|
headers: createApiHeaders(config, locale),
|
|
6525
6640
|
body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
|
|
@@ -6555,7 +6670,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6555
6670
|
setPaymentError(validationError);
|
|
6556
6671
|
return;
|
|
6557
6672
|
}
|
|
6558
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6673
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6674
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6675
|
+
: "/booking/create-mollie-payment"), {
|
|
6559
6676
|
method: "POST",
|
|
6560
6677
|
headers: createApiHeaders(config, locale),
|
|
6561
6678
|
body: JSON.stringify(createRequestBody(config, buildRequestData())),
|
|
@@ -6583,7 +6700,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6583
6700
|
? t("button.processingPayment")
|
|
6584
6701
|
: selectedMethodData
|
|
6585
6702
|
? t("payment.payWithMethod", { method: selectedMethodData.description })
|
|
6586
|
-
: totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6703
|
+
: !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6587
6704
|
? t("button.depositAndBook")
|
|
6588
6705
|
: t("button.bookNow");
|
|
6589
6706
|
const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
|
|
@@ -6890,12 +7007,18 @@ var loadStripe = function loadStripe() {
|
|
|
6890
7007
|
};
|
|
6891
7008
|
|
|
6892
7009
|
// Inner component that uses the Stripe hooks
|
|
6893
|
-
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
|
|
7010
|
+
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
|
|
6894
7011
|
const t = useTranslations();
|
|
6895
|
-
const
|
|
6896
|
-
const
|
|
6897
|
-
|
|
7012
|
+
const isFreeform = Boolean(freeformSelection);
|
|
7013
|
+
const participantCount = isFreeform
|
|
7014
|
+
? 1
|
|
7015
|
+
: formData.participants.filter((p) => p.name.trim()).length;
|
|
7016
|
+
const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
|
|
7017
|
+
const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
|
|
6898
7018
|
return (jsx(StripeElementsPaymentForm, { onValidate: () => {
|
|
7019
|
+
if (isFreeform) {
|
|
7020
|
+
return null;
|
|
7021
|
+
}
|
|
6899
7022
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6900
7023
|
if (participantCount > availableSpots) {
|
|
6901
7024
|
return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
|
|
@@ -6917,7 +7040,7 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
|
|
|
6917
7040
|
},
|
|
6918
7041
|
}, submitContent: jsx(Fragment, { children: submitLabel }), loadingContent: jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
|
|
6919
7042
|
}
|
|
6920
|
-
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
|
|
7043
|
+
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
|
|
6921
7044
|
const t = useTranslations();
|
|
6922
7045
|
const { locale } = useLocale();
|
|
6923
7046
|
const [clientSecret, setClientSecret] = useState(null);
|
|
@@ -6937,7 +7060,9 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6937
7060
|
return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
|
|
6938
7061
|
}, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
|
|
6939
7062
|
const storageKey = typeof window !== "undefined"
|
|
6940
|
-
?
|
|
7063
|
+
? freeformSelection
|
|
7064
|
+
? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
|
|
7065
|
+
: `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
|
|
6941
7066
|
: "";
|
|
6942
7067
|
const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
|
|
6943
7068
|
function loadPersistedPaymentIntent() {
|
|
@@ -6992,48 +7117,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6992
7117
|
}, [paymentIntentId]);
|
|
6993
7118
|
useEffect(() => {
|
|
6994
7119
|
const createStripePayment = async () => {
|
|
6995
|
-
if (!systemConfig || !eventDetails
|
|
7120
|
+
if (!systemConfig || !eventDetails) {
|
|
7121
|
+
return;
|
|
7122
|
+
}
|
|
7123
|
+
if (freeformSelection) {
|
|
7124
|
+
if (!freeformSelection.customerEmail?.trim() ||
|
|
7125
|
+
!freeformSelection.customerName?.trim() ||
|
|
7126
|
+
!freeformSelection.start ||
|
|
7127
|
+
!freeformSelection.end) {
|
|
7128
|
+
return;
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
else if (!formData.participants?.length) {
|
|
6996
7132
|
return;
|
|
6997
7133
|
}
|
|
6998
7134
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6999
|
-
if (
|
|
7135
|
+
if (!freeformSelection &&
|
|
7136
|
+
(participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
|
|
7000
7137
|
return;
|
|
7001
7138
|
}
|
|
7002
7139
|
setIsCreatingPaymentIntent(true);
|
|
7003
7140
|
setPaymentError(null);
|
|
7004
7141
|
try {
|
|
7005
|
-
const
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7142
|
+
const endpoint = freeformSelection
|
|
7143
|
+
? "/booking/create-freeform-stripe-payment"
|
|
7144
|
+
: "/booking/create-stripe-payment";
|
|
7145
|
+
const requestData = freeformSelection
|
|
7146
|
+
? {
|
|
7147
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
7148
|
+
organizationId: config.organizationId,
|
|
7149
|
+
start: freeformSelection.start,
|
|
7150
|
+
end: freeformSelection.end,
|
|
7151
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
7152
|
+
amount: Math.round(totalAmount),
|
|
7153
|
+
currency: "eur",
|
|
7154
|
+
customerName: freeformSelection.customerName.trim(),
|
|
7155
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
7156
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
7157
|
+
comment: freeformSelection.comment?.trim(),
|
|
7158
|
+
locale,
|
|
7159
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7160
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7161
|
+
}
|
|
7162
|
+
: {
|
|
7163
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
7164
|
+
organizationId: config.organizationId,
|
|
7165
|
+
amount: Math.round(totalAmount),
|
|
7166
|
+
currency: "eur",
|
|
7167
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
7168
|
+
discountCode: discountCode?.code,
|
|
7169
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
7170
|
+
customerName: formData.customerName?.trim(),
|
|
7171
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
7172
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
7173
|
+
comment: formData.comment?.trim(),
|
|
7174
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7175
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7176
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
7177
|
+
};
|
|
7024
7178
|
if (!requestData.organizationId) {
|
|
7025
7179
|
throw new Error("Organization ID is required");
|
|
7026
7180
|
}
|
|
7027
|
-
if (
|
|
7181
|
+
if (requestData.amount === undefined || requestData.amount < 0) {
|
|
7028
7182
|
throw new Error("Valid amount is required");
|
|
7029
7183
|
}
|
|
7030
|
-
if (!requestData
|
|
7184
|
+
if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
|
|
7185
|
+
throw new Error("Event instance ID is required");
|
|
7186
|
+
}
|
|
7187
|
+
if (!freeformSelection &&
|
|
7188
|
+
"participants" in requestData &&
|
|
7189
|
+
(!requestData.participants || requestData.participants.length === 0)) {
|
|
7031
7190
|
throw new Error("At least one participant is required");
|
|
7032
7191
|
}
|
|
7033
7192
|
if (!requestData.customerEmail) {
|
|
7034
7193
|
throw new Error("Customer email is required");
|
|
7035
7194
|
}
|
|
7036
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
7195
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
|
|
7037
7196
|
method: "POST",
|
|
7038
7197
|
headers: createApiHeaders(config, locale),
|
|
7039
7198
|
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
@@ -7078,9 +7237,13 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7078
7237
|
giftCards,
|
|
7079
7238
|
config,
|
|
7080
7239
|
upsellSelections,
|
|
7240
|
+
freeformSelection,
|
|
7241
|
+
locale,
|
|
7081
7242
|
]);
|
|
7082
7243
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
7083
|
-
const isFullyCoveredByGiftCards =
|
|
7244
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
7245
|
+
? false
|
|
7246
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
7084
7247
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
7085
7248
|
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
7086
7249
|
}
|
|
@@ -7119,7 +7282,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7119
7282
|
setPaymentIntentId(null);
|
|
7120
7283
|
setClientSecret(null);
|
|
7121
7284
|
onSuccess(result);
|
|
7122
|
-
}, onError: onError }) }));
|
|
7285
|
+
}, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
|
|
7123
7286
|
}
|
|
7124
7287
|
|
|
7125
7288
|
const HOLD_DURATION_SECONDS = 15 * 60;
|
|
@@ -13379,20 +13542,22 @@ const sectionHeaderStyles = sectionStyles.header;
|
|
|
13379
13542
|
const labelStyles = formStyles.label;
|
|
13380
13543
|
const inputStyles = formStyles.input;
|
|
13381
13544
|
const errorTextStyles = formStyles.error;
|
|
13382
|
-
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13545
|
+
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13383
13546
|
const t = useTranslations();
|
|
13384
13547
|
const { locale } = useLocale();
|
|
13385
13548
|
const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
|
|
13386
13549
|
// Form state
|
|
13387
|
-
// Show voucher type selection if both monetary and event vouchers are allowed
|
|
13388
|
-
// The event type selection will handle the case when no event types are available
|
|
13389
13550
|
const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
|
|
13390
13551
|
const canUseEventVoucherType = voucherConfig.allowEventVouchers;
|
|
13391
|
-
const
|
|
13552
|
+
const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
|
|
13553
|
+
const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
|
|
13554
|
+
const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
|
|
13392
13555
|
const [voucherType, setVoucherType] = useState(canUseMonetaryVoucherType ? "monetary" : "event");
|
|
13393
13556
|
const [monetaryAmount, setMonetaryAmount] = useState(voucherConfig.monetaryPresets[2] || 10000);
|
|
13394
13557
|
const [selectedEventTypeId, setSelectedEventTypeId] = useState(null);
|
|
13395
13558
|
const [eventQuantity, setEventQuantity] = useState(1);
|
|
13559
|
+
const [selectedCategoryId, setSelectedCategoryId] = useState(null);
|
|
13560
|
+
const [categoryAmount, setCategoryAmount] = useState(10000);
|
|
13396
13561
|
const [recipientName, setRecipientName] = useState("");
|
|
13397
13562
|
const [message, setMessage] = useState("");
|
|
13398
13563
|
const [customerName, setCustomerName] = useState("");
|
|
@@ -13452,6 +13617,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13452
13617
|
if (voucherType === "monetary") {
|
|
13453
13618
|
return monetaryAmount;
|
|
13454
13619
|
}
|
|
13620
|
+
else if (voucherType === "category") {
|
|
13621
|
+
return categoryAmount;
|
|
13622
|
+
}
|
|
13455
13623
|
else {
|
|
13456
13624
|
const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13457
13625
|
if (eventType) {
|
|
@@ -13459,7 +13627,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13459
13627
|
}
|
|
13460
13628
|
return 0;
|
|
13461
13629
|
}
|
|
13462
|
-
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
|
|
13630
|
+
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
|
|
13463
13631
|
const selectedEventType = useMemo(() => {
|
|
13464
13632
|
return eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13465
13633
|
}, [eventTypes, selectedEventTypeId]);
|
|
@@ -13468,6 +13636,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13468
13636
|
voucherType,
|
|
13469
13637
|
selectedEventTypeId,
|
|
13470
13638
|
eventQuantity,
|
|
13639
|
+
selectedCategoryId,
|
|
13640
|
+
categoryAmount,
|
|
13471
13641
|
recipientName: recipientName.trim(),
|
|
13472
13642
|
message: message.trim(),
|
|
13473
13643
|
customerName: customerName.trim(),
|
|
@@ -13477,6 +13647,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13477
13647
|
voucherType,
|
|
13478
13648
|
selectedEventTypeId,
|
|
13479
13649
|
eventQuantity,
|
|
13650
|
+
selectedCategoryId,
|
|
13651
|
+
categoryAmount,
|
|
13480
13652
|
recipientName,
|
|
13481
13653
|
message,
|
|
13482
13654
|
customerName,
|
|
@@ -13495,24 +13667,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13495
13667
|
return false;
|
|
13496
13668
|
if (voucherType === "event" && !selectedEventTypeId)
|
|
13497
13669
|
return false;
|
|
13670
|
+
if (voucherType === "category" && !selectedCategoryId)
|
|
13671
|
+
return false;
|
|
13498
13672
|
return true;
|
|
13499
|
-
}, [customerName, customerEmail, acceptTerms, totalAmount,
|
|
13673
|
+
}, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
|
|
13500
13674
|
const defaultMonetaryAmount = useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
|
|
13501
13675
|
const handleVoucherTypeChange = useCallback((nextType) => {
|
|
13502
13676
|
setVoucherType(nextType);
|
|
13503
13677
|
setPaymentError(null);
|
|
13504
13678
|
if (nextType === "event") {
|
|
13505
|
-
// Keep voucher type mutually exclusive: entering event flow resets amount selection state.
|
|
13506
13679
|
setMonetaryAmount(defaultMonetaryAmount);
|
|
13680
|
+
setSelectedCategoryId(null);
|
|
13681
|
+
setCategoryAmount(10000);
|
|
13507
13682
|
if (eventTypes.length === 1) {
|
|
13508
13683
|
setSelectedEventTypeId(eventTypes[0]?.id ?? null);
|
|
13509
13684
|
}
|
|
13510
13685
|
return;
|
|
13511
13686
|
}
|
|
13512
|
-
|
|
13687
|
+
if (nextType === "category") {
|
|
13688
|
+
setMonetaryAmount(defaultMonetaryAmount);
|
|
13689
|
+
setSelectedEventTypeId(null);
|
|
13690
|
+
setEventQuantity(1);
|
|
13691
|
+
if (categories.length === 1) {
|
|
13692
|
+
setSelectedCategoryId(categories[0]?.id ?? null);
|
|
13693
|
+
}
|
|
13694
|
+
return;
|
|
13695
|
+
}
|
|
13513
13696
|
setSelectedEventTypeId(null);
|
|
13514
13697
|
setEventQuantity(1);
|
|
13515
|
-
|
|
13698
|
+
setSelectedCategoryId(null);
|
|
13699
|
+
setCategoryAmount(10000);
|
|
13700
|
+
}, [defaultMonetaryAmount, eventTypes, categories]);
|
|
13516
13701
|
useEffect(() => {
|
|
13517
13702
|
if (!canUseMonetaryVoucherType && voucherType === "monetary") {
|
|
13518
13703
|
setVoucherType("event");
|
|
@@ -13553,9 +13738,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13553
13738
|
organizationId: config.organizationId,
|
|
13554
13739
|
amount: totalAmount,
|
|
13555
13740
|
currency: "eur",
|
|
13556
|
-
voucherType,
|
|
13557
|
-
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13741
|
+
voucherType: voucherType === "category" ? "monetary" : voucherType,
|
|
13742
|
+
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13743
|
+
: voucherType === "category" ? categoryAmount
|
|
13744
|
+
: undefined,
|
|
13558
13745
|
eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
|
|
13746
|
+
eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
|
|
13559
13747
|
eventAmount: voucherType === "event" ? eventQuantity : undefined,
|
|
13560
13748
|
recipientName: recipientName.trim() || undefined,
|
|
13561
13749
|
message: message.trim() || undefined,
|
|
@@ -13746,7 +13934,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13746
13934
|
fontSize: "13px",
|
|
13747
13935
|
color: "var(--bw-text-muted)",
|
|
13748
13936
|
marginTop: "4px",
|
|
13749
|
-
}, children: t("voucher.eventTypeDesc") })] }))
|
|
13937
|
+
}, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
|
|
13938
|
+
flex: 1,
|
|
13939
|
+
padding: "16px",
|
|
13940
|
+
borderRadius: "var(--bw-border-radius)",
|
|
13941
|
+
border: voucherType === "category"
|
|
13942
|
+
? "2px solid var(--bw-highlight-color)"
|
|
13943
|
+
: "1px solid var(--bw-border-color)",
|
|
13944
|
+
backgroundColor: voucherType === "category"
|
|
13945
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
13946
|
+
: "var(--bw-surface-color)",
|
|
13947
|
+
cursor: "pointer",
|
|
13948
|
+
fontFamily: "var(--bw-font-family)",
|
|
13949
|
+
transition: "all 0.2s ease",
|
|
13950
|
+
textAlign: "center",
|
|
13951
|
+
}, children: [jsx("div", { style: {
|
|
13952
|
+
fontWeight: 600,
|
|
13953
|
+
color: voucherType === "category"
|
|
13954
|
+
? "var(--bw-highlight-color)"
|
|
13955
|
+
: "var(--bw-text-color)",
|
|
13956
|
+
}, children: t("voucher.categoryType") }), jsx("div", { style: {
|
|
13957
|
+
fontSize: "13px",
|
|
13958
|
+
color: "var(--bw-text-muted)",
|
|
13959
|
+
marginTop: "4px",
|
|
13960
|
+
}, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsx("div", { style: {
|
|
13750
13961
|
display: "grid",
|
|
13751
13962
|
gridTemplateColumns: "repeat(3, 1fr)",
|
|
13752
13963
|
gap: "8px",
|
|
@@ -13801,7 +14012,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13801
14012
|
fontSize: "18px",
|
|
13802
14013
|
color: "var(--bw-highlight-color)",
|
|
13803
14014
|
fontFamily: "var(--bw-font-family)",
|
|
13804
|
-
}, children: formatCurrency(totalAmount) })] }) }))] })), jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.
|
|
14015
|
+
}, 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: {
|
|
14016
|
+
...inputStyles,
|
|
14017
|
+
cursor: categories.length === 1 ? "not-allowed" : "pointer",
|
|
14018
|
+
}, 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: {
|
|
14019
|
+
display: "grid",
|
|
14020
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
14021
|
+
gap: "8px",
|
|
14022
|
+
marginTop: "4px",
|
|
14023
|
+
}, children: voucherConfig.monetaryPresets.map((preset) => (jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
|
|
14024
|
+
padding: "10px",
|
|
14025
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14026
|
+
border: categoryAmount === preset
|
|
14027
|
+
? "2px solid var(--bw-highlight-color)"
|
|
14028
|
+
: "1px solid var(--bw-border-color)",
|
|
14029
|
+
backgroundColor: categoryAmount === preset
|
|
14030
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
14031
|
+
: "var(--bw-surface-color)",
|
|
14032
|
+
cursor: "pointer",
|
|
14033
|
+
fontFamily: "var(--bw-font-family)",
|
|
14034
|
+
fontWeight: 600,
|
|
14035
|
+
fontSize: "14px",
|
|
14036
|
+
color: categoryAmount === preset
|
|
14037
|
+
? "var(--bw-highlight-color)"
|
|
14038
|
+
: "var(--bw-text-color)",
|
|
14039
|
+
transition: "all 0.2s ease",
|
|
14040
|
+
}, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsx("div", { style: {
|
|
14041
|
+
marginTop: "16px",
|
|
14042
|
+
padding: "12px",
|
|
14043
|
+
backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
|
|
14044
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14045
|
+
border: "1px solid var(--bw-highlight-color)",
|
|
14046
|
+
}, children: jsxs("div", { style: {
|
|
14047
|
+
display: "flex",
|
|
14048
|
+
justifyContent: "space-between",
|
|
14049
|
+
alignItems: "center",
|
|
14050
|
+
}, children: [jsx("span", { style: {
|
|
14051
|
+
color: "var(--bw-text-color)",
|
|
14052
|
+
fontFamily: "var(--bw-font-family)",
|
|
14053
|
+
}, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsx("span", { style: {
|
|
14054
|
+
fontWeight: 700,
|
|
14055
|
+
fontSize: "18px",
|
|
14056
|
+
color: "var(--bw-highlight-color)",
|
|
14057
|
+
fontFamily: "var(--bw-font-family)",
|
|
14058
|
+
}, children: formatCurrency(categoryAmount) })] }) }))] })), jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsx("p", { style: {
|
|
13805
14059
|
fontSize: "13px",
|
|
13806
14060
|
color: "var(--bw-text-muted)",
|
|
13807
14061
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -13834,7 +14088,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13834
14088
|
fontFamily: "var(--bw-font-family)",
|
|
13835
14089
|
}, children: voucherType === "monetary"
|
|
13836
14090
|
? t("voucher.monetaryVoucher")
|
|
13837
|
-
:
|
|
14091
|
+
: voucherType === "category"
|
|
14092
|
+
? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
|
|
14093
|
+
: `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxs("div", { style: {
|
|
13838
14094
|
fontSize: "13px",
|
|
13839
14095
|
color: "var(--bw-text-muted)",
|
|
13840
14096
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -14110,7 +14366,7 @@ function VoucherAttachment({ onClick }) {
|
|
|
14110
14366
|
}, children: t("voucher.buyAsGift") })] }));
|
|
14111
14367
|
}
|
|
14112
14368
|
|
|
14113
|
-
function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14369
|
+
function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14114
14370
|
if (!voucherConfig?.enabled) {
|
|
14115
14371
|
return null;
|
|
14116
14372
|
}
|
|
@@ -14121,7 +14377,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
|
|
|
14121
14377
|
justifyContent: "center",
|
|
14122
14378
|
}, children: jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
|
|
14123
14379
|
? Math.min(...eventTypes.map((et) => et.maxPrice))
|
|
14124
|
-
: 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 }))] }));
|
|
14380
|
+
: 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 }))] }));
|
|
14125
14381
|
}
|
|
14126
14382
|
|
|
14127
14383
|
// Helper function to preprocess markdown for underline support
|
|
@@ -14181,7 +14437,7 @@ function InfoBadge({ text }) {
|
|
|
14181
14437
|
pointerEvents: "none",
|
|
14182
14438
|
}, children: text }))] }));
|
|
14183
14439
|
}
|
|
14184
|
-
function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
|
|
14440
|
+
function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, showImages = true, }) {
|
|
14185
14441
|
const t = useTranslations();
|
|
14186
14442
|
const { locale } = useLocale();
|
|
14187
14443
|
const timezone = useTimezone();
|
|
@@ -14260,6 +14516,18 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14260
14516
|
gridAutoRows: "1fr",
|
|
14261
14517
|
}, children: eventTypes.map((eventType) => {
|
|
14262
14518
|
const isAvailable = eventType.hasAvailableInstances;
|
|
14519
|
+
const hasImage = showImages && eventType.images && eventType.images.length > 0;
|
|
14520
|
+
const availabilityBadge = (jsx("div", { style: {
|
|
14521
|
+
padding: "4px 8px",
|
|
14522
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
14523
|
+
fontSize: "12px",
|
|
14524
|
+
fontWeight: 600,
|
|
14525
|
+
color: "#ffffff",
|
|
14526
|
+
fontFamily: "var(--bw-font-family)",
|
|
14527
|
+
backgroundColor: isAvailable
|
|
14528
|
+
? "var(--bw-success-color)"
|
|
14529
|
+
: "var(--bw-error-color)",
|
|
14530
|
+
}, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }));
|
|
14263
14531
|
return (jsxs("div", { className: "event-type-card", style: {
|
|
14264
14532
|
position: "relative",
|
|
14265
14533
|
backgroundColor: "var(--bw-surface-color)",
|
|
@@ -14271,17 +14539,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14271
14539
|
fontFamily: "var(--bw-font-family)",
|
|
14272
14540
|
cursor: isAvailable ? "pointer" : "not-allowed",
|
|
14273
14541
|
opacity: isAvailable ? 1 : 0.6,
|
|
14274
|
-
}, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: jsx("div", { style: {
|
|
14275
|
-
padding: "4px 8px",
|
|
14276
|
-
borderRadius: "var(--bw-border-radius-small)",
|
|
14277
|
-
fontSize: "12px",
|
|
14278
|
-
fontWeight: 600,
|
|
14279
|
-
color: "#ffffff",
|
|
14280
|
-
fontFamily: "var(--bw-font-family)",
|
|
14281
|
-
backgroundColor: isAvailable
|
|
14282
|
-
? "var(--bw-success-color)"
|
|
14283
|
-
: "var(--bw-error-color)",
|
|
14284
|
-
}, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }) }), eventType.images && eventType.images.length > 0 && (jsxs(Fragment, { children: [jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsx("div", { style: {
|
|
14542
|
+
}, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [hasImage && (jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: availabilityBadge })), hasImage && (jsxs(Fragment, { children: [jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsx("div", { style: {
|
|
14285
14543
|
fontSize: "13px",
|
|
14286
14544
|
color: "var(--bw-surface-color)",
|
|
14287
14545
|
fontWeight: 600,
|
|
@@ -14295,7 +14553,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14295
14553
|
flexDirection: "column",
|
|
14296
14554
|
justifyContent: "space-between",
|
|
14297
14555
|
height: "540px",
|
|
14298
|
-
}, children: [jsxs("div", { children: [jsx("h2", { className: "event-type-title", style: {
|
|
14556
|
+
}, children: [jsxs("div", { children: [!hasImage && (jsx("div", { style: { display: "flex", justifyContent: "flex-end", margin: "0 0 8px 0" }, children: availabilityBadge })), jsx("h2", { className: "event-type-title", style: {
|
|
14299
14557
|
fontSize: "clamp(1.1rem, 2.5vw, 24px)",
|
|
14300
14558
|
fontWeight: 700,
|
|
14301
14559
|
color: "var(--bw-text-color)",
|
|
@@ -14567,16 +14825,16 @@ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName
|
|
|
14567
14825
|
}
|
|
14568
14826
|
|
|
14569
14827
|
const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
|
|
14570
|
-
|
|
14571
|
-
if (availabilityRatio >= 0.6)
|
|
14572
|
-
return null;
|
|
14573
|
-
if (availabilityRatio === 0) {
|
|
14828
|
+
if (maxParticipants <= 0 || availableSpots <= 0) {
|
|
14574
14829
|
return {
|
|
14575
14830
|
text: t("events.soldOut"),
|
|
14576
14831
|
backgroundColor: "#7f1d1d",
|
|
14577
14832
|
textColor: "#fca5a5",
|
|
14578
14833
|
};
|
|
14579
14834
|
}
|
|
14835
|
+
const availabilityRatio = availableSpots / maxParticipants;
|
|
14836
|
+
if (availabilityRatio >= 0.6)
|
|
14837
|
+
return null;
|
|
14580
14838
|
if (availabilityRatio <= 0.3) {
|
|
14581
14839
|
return {
|
|
14582
14840
|
text: t("instances.almostSoldOut"),
|
|
@@ -14690,7 +14948,7 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
|
|
|
14690
14948
|
boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
|
|
14691
14949
|
}, children: formatCurrency(price) }));
|
|
14692
14950
|
};
|
|
14693
|
-
function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
|
|
14951
|
+
function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, waitlistEnabled = false, }) {
|
|
14694
14952
|
const t = useTranslations();
|
|
14695
14953
|
const { locale } = useLocale();
|
|
14696
14954
|
const timezone = useTimezone();
|
|
@@ -14875,12 +15133,13 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
|
|
|
14875
15133
|
? "0 2px 4px rgba(0, 0, 0, 0.2)"
|
|
14876
15134
|
: undefined,
|
|
14877
15135
|
}, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
|
|
14878
|
-
const availableSpots = event.
|
|
14879
|
-
const isFullyBooked = availableSpots === 0;
|
|
15136
|
+
const availableSpots = event.availableSpots;
|
|
14880
15137
|
const startDate = new Date(event.startTime);
|
|
14881
15138
|
const isPastEvent = today.toISOString() >= startDate.toISOString();
|
|
14882
|
-
const
|
|
14883
|
-
const
|
|
15139
|
+
const isUnavailable = availableSpots <= 0 || !event.bookingOpen;
|
|
15140
|
+
const isDisabled = isUnavailable || isPastEvent;
|
|
15141
|
+
const canWaitlist = waitlistEnabled &&
|
|
15142
|
+
isUnavailable &&
|
|
14884
15143
|
!isPastEvent &&
|
|
14885
15144
|
!!apiBaseUrl &&
|
|
14886
15145
|
!!organizationId;
|
|
@@ -14917,7 +15176,7 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
|
|
|
14917
15176
|
color: "var(--bw-highlight-color)",
|
|
14918
15177
|
opacity: 0.8,
|
|
14919
15178
|
fontSize: "32px",
|
|
14920
|
-
}, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
|
|
15179
|
+
}, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.effectiveMaxParticipants ?? event.maxParticipants, t: t }), jsxs("div", { style: {
|
|
14921
15180
|
display: "flex",
|
|
14922
15181
|
justifyContent: "space-between",
|
|
14923
15182
|
width: "100%",
|
|
@@ -15094,18 +15353,19 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
|
|
|
15094
15353
|
margin: "0 auto",
|
|
15095
15354
|
padding: "12px 16px",
|
|
15096
15355
|
fontFamily: "var(--bw-font-family)",
|
|
15097
|
-
}, children: [jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events
|
|
15098
|
-
|
|
15099
|
-
const isFullyBooked = availableSpots === 0;
|
|
15356
|
+
}, children: [jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events
|
|
15357
|
+
.filter((event) => {
|
|
15100
15358
|
const startDate = new Date(event.startTime);
|
|
15101
15359
|
const isPastEvent = today.toISOString() >= startDate.toISOString();
|
|
15102
|
-
|
|
15103
|
-
|
|
15360
|
+
return !isPastEvent && event.availableSpots > 0 && event.bookingOpen;
|
|
15361
|
+
})
|
|
15362
|
+
.map((event) => {
|
|
15363
|
+
const availableSpots = event.availableSpots;
|
|
15364
|
+
const startDate = new Date(event.startTime);
|
|
15104
15365
|
const allocationBadge = (() => {
|
|
15366
|
+
const availabilityRatio = availableSpots / event.maxParticipants;
|
|
15105
15367
|
if (availabilityRatio >= 0.6)
|
|
15106
15368
|
return null;
|
|
15107
|
-
if (availabilityRatio === 0)
|
|
15108
|
-
return { text: t("events.soldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
|
|
15109
15369
|
if (availabilityRatio <= 0.3)
|
|
15110
15370
|
return { text: t("instances.almostSoldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
|
|
15111
15371
|
return { text: t("instances.popularDate"), backgroundColor: "#b45309", textColor: "#fbbf24" };
|
|
@@ -15119,13 +15379,9 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
|
|
|
15119
15379
|
padding: "16px 10px",
|
|
15120
15380
|
transition: "all 0.2s ease",
|
|
15121
15381
|
fontFamily: "var(--bw-font-family)",
|
|
15122
|
-
|
|
15123
|
-
filter: isDisabled ? "grayscale(40%)" : "none",
|
|
15124
|
-
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
15382
|
+
cursor: "pointer",
|
|
15125
15383
|
}, onClick: () => {
|
|
15126
|
-
|
|
15127
|
-
handleEventSelect(event.id);
|
|
15128
|
-
}
|
|
15384
|
+
handleEventSelect(event.id);
|
|
15129
15385
|
}, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
|
|
15130
15386
|
position: "absolute",
|
|
15131
15387
|
inset: 0,
|
|
@@ -15380,7 +15636,7 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
|
|
|
15380
15636
|
alignItems: "center",
|
|
15381
15637
|
backgroundColor: "var(--bw-background-color)",
|
|
15382
15638
|
}, children: [jsx("span", { style: { fontSize: "12px", color: "var(--bw-text-muted)" }, children: isFullyBooked
|
|
15383
|
-
? t("
|
|
15639
|
+
? t("events.soldOut")
|
|
15384
15640
|
: t("specials.spotsLeft").replace("{{count}}", String(special.availableSpots)) }), jsxs("span", { style: {
|
|
15385
15641
|
fontSize: "13px",
|
|
15386
15642
|
fontWeight: 600,
|
|
@@ -15389,6 +15645,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
|
|
|
15389
15645
|
}) })] }));
|
|
15390
15646
|
}
|
|
15391
15647
|
|
|
15648
|
+
function toMinutesForUnit(unit) {
|
|
15649
|
+
switch (unit.toLowerCase()) {
|
|
15650
|
+
case "minute":
|
|
15651
|
+
case "minutes":
|
|
15652
|
+
return 1;
|
|
15653
|
+
case "hour":
|
|
15654
|
+
case "hours":
|
|
15655
|
+
return 60;
|
|
15656
|
+
case "day":
|
|
15657
|
+
case "days":
|
|
15658
|
+
return 24 * 60;
|
|
15659
|
+
default:
|
|
15660
|
+
return 1;
|
|
15661
|
+
}
|
|
15662
|
+
}
|
|
15663
|
+
// --- Timezone helpers ------------------------------------------------------
|
|
15664
|
+
// The booking is evaluated server-side against the organization's local
|
|
15665
|
+
// operating hours. The customer's browser may be in a different timezone, so we
|
|
15666
|
+
// interpret the datetime-local field as a wall-clock time in the ORG timezone
|
|
15667
|
+
// (not the browser's) and convert to a UTC instant for the API.
|
|
15668
|
+
function getTzParts(date, timeZone) {
|
|
15669
|
+
const dtf = new Intl.DateTimeFormat("en-CA", {
|
|
15670
|
+
timeZone,
|
|
15671
|
+
hour12: false,
|
|
15672
|
+
year: "numeric",
|
|
15673
|
+
month: "2-digit",
|
|
15674
|
+
day: "2-digit",
|
|
15675
|
+
hour: "2-digit",
|
|
15676
|
+
minute: "2-digit",
|
|
15677
|
+
second: "2-digit",
|
|
15678
|
+
});
|
|
15679
|
+
const parts = {};
|
|
15680
|
+
for (const part of dtf.formatToParts(date)) {
|
|
15681
|
+
if (part.type !== "literal")
|
|
15682
|
+
parts[part.type] = part.value;
|
|
15683
|
+
}
|
|
15684
|
+
return parts;
|
|
15685
|
+
}
|
|
15686
|
+
function tzOffsetMs(utcMs, timeZone) {
|
|
15687
|
+
const p = getTzParts(new Date(utcMs), timeZone);
|
|
15688
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15689
|
+
const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
|
|
15690
|
+
return asLocalUtc - utcMs;
|
|
15691
|
+
}
|
|
15692
|
+
/** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
|
|
15693
|
+
function zonedWallTimeToUtc(wallTime, timeZone) {
|
|
15694
|
+
const [datePart, timePart] = wallTime.split("T");
|
|
15695
|
+
if (!datePart || !timePart)
|
|
15696
|
+
return new Date(NaN);
|
|
15697
|
+
const [y, mo, d] = datePart.split("-").map(Number);
|
|
15698
|
+
const [h, mi] = timePart.split(":").map(Number);
|
|
15699
|
+
const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
|
|
15700
|
+
// Single-pass offset correction (good enough outside the ~1h DST fold).
|
|
15701
|
+
const offset = tzOffsetMs(naiveUtc, timeZone);
|
|
15702
|
+
return new Date(naiveUtc - offset);
|
|
15703
|
+
}
|
|
15704
|
+
/** Render a UTC instant as an org-tz datetime-local input value. */
|
|
15705
|
+
function utcToZonedInputValue(date, timeZone) {
|
|
15706
|
+
const p = getTzParts(date, timeZone);
|
|
15707
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15708
|
+
return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
|
|
15709
|
+
}
|
|
15710
|
+
/** Format a UTC instant for display in the org timezone. */
|
|
15711
|
+
function formatInTimeZone(date, timeZone, locale) {
|
|
15712
|
+
return new Intl.DateTimeFormat(locale, {
|
|
15713
|
+
timeZone,
|
|
15714
|
+
dateStyle: "medium",
|
|
15715
|
+
timeStyle: "short",
|
|
15716
|
+
}).format(date);
|
|
15717
|
+
}
|
|
15718
|
+
function calculateTieredPrice(durationUnits, config) {
|
|
15719
|
+
const tiers = [...(config.tiers ?? [])]
|
|
15720
|
+
.filter((tier) => tier.unitCount > 0 && tier.price >= 0)
|
|
15721
|
+
.sort((a, b) => b.unitCount - a.unitCount);
|
|
15722
|
+
if (tiers.length === 0) {
|
|
15723
|
+
return 0;
|
|
15724
|
+
}
|
|
15725
|
+
let total = 0;
|
|
15726
|
+
let remaining = durationUnits;
|
|
15727
|
+
for (const tier of tiers) {
|
|
15728
|
+
const count = Math.floor(remaining / tier.unitCount);
|
|
15729
|
+
if (count <= 0)
|
|
15730
|
+
continue;
|
|
15731
|
+
total += count * tier.price;
|
|
15732
|
+
remaining -= count * tier.unitCount;
|
|
15733
|
+
}
|
|
15734
|
+
if (remaining > 0) {
|
|
15735
|
+
if (typeof config.perUnitPrice === "number") {
|
|
15736
|
+
total += remaining * config.perUnitPrice;
|
|
15737
|
+
}
|
|
15738
|
+
else {
|
|
15739
|
+
total += tiers[tiers.length - 1]?.price ?? 0;
|
|
15740
|
+
}
|
|
15741
|
+
}
|
|
15742
|
+
return total;
|
|
15743
|
+
}
|
|
15744
|
+
function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
|
|
15745
|
+
const t = useTranslations();
|
|
15746
|
+
const { locale } = useLocale();
|
|
15747
|
+
const freeformConfig = eventType.freeformConfig;
|
|
15748
|
+
const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
15749
|
+
const isNamedResource = eventType.resourceKind === "named";
|
|
15750
|
+
const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
|
|
15751
|
+
const now = useMemo(() => {
|
|
15752
|
+
const next = new Date();
|
|
15753
|
+
next.setMinutes(next.getMinutes() + 30, 0, 0);
|
|
15754
|
+
return next;
|
|
15755
|
+
}, []);
|
|
15756
|
+
const [startInput, setStartInput] = useState(utcToZonedInputValue(now, timeZone));
|
|
15757
|
+
const [duration, setDuration] = useState(Math.max(1, freeformConfig?.minDuration ?? 1));
|
|
15758
|
+
const [requestedUnits, setRequestedUnits] = useState(1);
|
|
15759
|
+
const [customerName, setCustomerName] = useState("");
|
|
15760
|
+
const [customerEmail, setCustomerEmail] = useState("");
|
|
15761
|
+
const [customerPhone, setCustomerPhone] = useState("");
|
|
15762
|
+
const [comment, setComment] = useState("");
|
|
15763
|
+
const [acceptTerms, setAcceptTerms] = useState(false);
|
|
15764
|
+
const [checkoutStep, setCheckoutStep] = useState("details");
|
|
15765
|
+
const [availability, setAvailability] = useState(null);
|
|
15766
|
+
const [availabilityError, setAvailabilityError] = useState(null);
|
|
15767
|
+
const [isCheckingAvailability, setIsCheckingAvailability] = useState(false);
|
|
15768
|
+
useEffect(() => {
|
|
15769
|
+
if (!isOpen)
|
|
15770
|
+
return;
|
|
15771
|
+
setCheckoutStep("details");
|
|
15772
|
+
}, [isOpen]);
|
|
15773
|
+
useEffect(() => {
|
|
15774
|
+
if (!freeformConfig)
|
|
15775
|
+
return;
|
|
15776
|
+
setDuration(Math.max(1, freeformConfig.minDuration));
|
|
15777
|
+
setRequestedUnits(1);
|
|
15778
|
+
setAvailability(null);
|
|
15779
|
+
setAvailabilityError(null);
|
|
15780
|
+
}, [freeformConfig?.resourceId]);
|
|
15781
|
+
const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
|
|
15782
|
+
const startDate = useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
|
|
15783
|
+
const endDate = useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
|
|
15784
|
+
useEffect(() => {
|
|
15785
|
+
if (!isOpen || !freeformConfig)
|
|
15786
|
+
return;
|
|
15787
|
+
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
|
|
15788
|
+
return;
|
|
15789
|
+
const timer = setTimeout(async () => {
|
|
15790
|
+
setIsCheckingAvailability(true);
|
|
15791
|
+
setAvailabilityError(null);
|
|
15792
|
+
try {
|
|
15793
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
|
|
15794
|
+
method: "POST",
|
|
15795
|
+
headers: createApiHeaders(config, locale),
|
|
15796
|
+
body: JSON.stringify(createRequestBody(config, {
|
|
15797
|
+
eventTypeId: eventType.id,
|
|
15798
|
+
requestedUnits,
|
|
15799
|
+
start: startDate.toISOString(),
|
|
15800
|
+
end: endDate.toISOString(),
|
|
15801
|
+
})),
|
|
15802
|
+
});
|
|
15803
|
+
const data = await response.json();
|
|
15804
|
+
if (!response.ok) {
|
|
15805
|
+
setAvailability(null);
|
|
15806
|
+
setAvailabilityError(data.error || t("error.loadBookingData"));
|
|
15807
|
+
return;
|
|
15808
|
+
}
|
|
15809
|
+
setAvailability({
|
|
15810
|
+
available: Boolean(data.available),
|
|
15811
|
+
freeUnits: Number(data.freeUnits ?? 0),
|
|
15812
|
+
capacityUnits: Number(data.capacityUnits ?? 0),
|
|
15813
|
+
usedUnits: Number(data.usedUnits ?? 0),
|
|
15814
|
+
reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
|
|
15815
|
+
blockUsedUnits: Number(data.blockUsedUnits ?? 0),
|
|
15816
|
+
durationUnits: Number(data.durationUnits ?? 0),
|
|
15817
|
+
roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
|
|
15818
|
+
});
|
|
15819
|
+
}
|
|
15820
|
+
catch (_error) {
|
|
15821
|
+
setAvailability(null);
|
|
15822
|
+
setAvailabilityError(t("error.loadBookingData"));
|
|
15823
|
+
}
|
|
15824
|
+
finally {
|
|
15825
|
+
setIsCheckingAvailability(false);
|
|
15826
|
+
}
|
|
15827
|
+
}, 350);
|
|
15828
|
+
return () => clearTimeout(timer);
|
|
15829
|
+
}, [
|
|
15830
|
+
config,
|
|
15831
|
+
endDate,
|
|
15832
|
+
eventType.id,
|
|
15833
|
+
freeformConfig,
|
|
15834
|
+
isOpen,
|
|
15835
|
+
locale,
|
|
15836
|
+
requestedUnits,
|
|
15837
|
+
startDate,
|
|
15838
|
+
t,
|
|
15839
|
+
]);
|
|
15840
|
+
const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
|
|
15841
|
+
const basePrice = useMemo(() => {
|
|
15842
|
+
if (!freeformConfig)
|
|
15843
|
+
return 0;
|
|
15844
|
+
if (freeformConfig.pricingModel === "perUnit") {
|
|
15845
|
+
return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
|
|
15846
|
+
}
|
|
15847
|
+
return calculateTieredPrice(billedDurationUnits, freeformConfig);
|
|
15848
|
+
}, [billedDurationUnits, freeformConfig]);
|
|
15849
|
+
const totalPrice = Math.max(0, basePrice * requestedUnits);
|
|
15850
|
+
const canContinueToPayment = !!freeformConfig &&
|
|
15851
|
+
customerName.trim().length >= 2 &&
|
|
15852
|
+
customerEmail.trim().length > 3 &&
|
|
15853
|
+
acceptTerms &&
|
|
15854
|
+
!!availability?.available &&
|
|
15855
|
+
!isCheckingAvailability;
|
|
15856
|
+
const freeformSelection = useMemo(() => ({
|
|
15857
|
+
eventTypeId: eventType.id,
|
|
15858
|
+
start: startDate.toISOString(),
|
|
15859
|
+
end: endDate.toISOString(),
|
|
15860
|
+
requestedUnits,
|
|
15861
|
+
customerName: customerName.trim(),
|
|
15862
|
+
customerEmail: customerEmail.trim(),
|
|
15863
|
+
customerPhone: customerPhone.trim(),
|
|
15864
|
+
comment: comment.trim(),
|
|
15865
|
+
}), [
|
|
15866
|
+
comment,
|
|
15867
|
+
customerEmail,
|
|
15868
|
+
customerName,
|
|
15869
|
+
customerPhone,
|
|
15870
|
+
endDate,
|
|
15871
|
+
eventType.id,
|
|
15872
|
+
requestedUnits,
|
|
15873
|
+
startDate,
|
|
15874
|
+
]);
|
|
15875
|
+
const paymentFormData = useMemo(() => ({
|
|
15876
|
+
customerName: customerName.trim(),
|
|
15877
|
+
customerEmail: customerEmail.trim(),
|
|
15878
|
+
customerPhone: customerPhone.trim(),
|
|
15879
|
+
participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
|
|
15880
|
+
comment: comment.trim(),
|
|
15881
|
+
}), [comment, customerEmail, customerName, customerPhone]);
|
|
15882
|
+
const eventDetailsForPayment = useMemo(() => ({
|
|
15883
|
+
id: eventType.id,
|
|
15884
|
+
name: eventType.name,
|
|
15885
|
+
startTime: startDate.toISOString(),
|
|
15886
|
+
endTime: endDate.toISOString(),
|
|
15887
|
+
price: basePrice,
|
|
15888
|
+
maxParticipants: availability?.capacityUnits ?? requestedUnits,
|
|
15889
|
+
participantCount: 0,
|
|
15890
|
+
availableSpots: availability?.freeUnits ?? requestedUnits,
|
|
15891
|
+
durationDays: 1,
|
|
15892
|
+
durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
|
|
15893
|
+
images: eventType.images ?? [],
|
|
15894
|
+
category: eventType.category,
|
|
15895
|
+
organization: {
|
|
15896
|
+
id: config.organizationId,
|
|
15897
|
+
name: "",
|
|
15898
|
+
},
|
|
15899
|
+
bookingOpen: availability?.available ?? false,
|
|
15900
|
+
}), [
|
|
15901
|
+
availability?.available,
|
|
15902
|
+
availability?.capacityUnits,
|
|
15903
|
+
availability?.freeUnits,
|
|
15904
|
+
availability?.roundedDurationMinutes,
|
|
15905
|
+
basePrice,
|
|
15906
|
+
config.organizationId,
|
|
15907
|
+
duration,
|
|
15908
|
+
endDate,
|
|
15909
|
+
eventType.category,
|
|
15910
|
+
eventType.id,
|
|
15911
|
+
eventType.images,
|
|
15912
|
+
eventType.name,
|
|
15913
|
+
requestedUnits,
|
|
15914
|
+
startDate,
|
|
15915
|
+
unitMinutes,
|
|
15916
|
+
]);
|
|
15917
|
+
const footer = (jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsx("button", { type: "button", onClick: () => {
|
|
15918
|
+
if (checkoutStep === "payment") {
|
|
15919
|
+
setCheckoutStep("details");
|
|
15920
|
+
}
|
|
15921
|
+
else {
|
|
15922
|
+
onClose();
|
|
15923
|
+
}
|
|
15924
|
+
}, style: {
|
|
15925
|
+
flex: 1,
|
|
15926
|
+
padding: "12px 16px",
|
|
15927
|
+
border: "1px solid var(--bw-border-color)",
|
|
15928
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15929
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
15930
|
+
color: "var(--bw-text-color)",
|
|
15931
|
+
fontFamily: "var(--bw-font-family)",
|
|
15932
|
+
fontWeight: 600,
|
|
15933
|
+
cursor: "pointer",
|
|
15934
|
+
}, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
|
|
15935
|
+
flex: 1,
|
|
15936
|
+
padding: "12px 16px",
|
|
15937
|
+
border: "none",
|
|
15938
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15939
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
15940
|
+
color: "var(--bw-button-text-color, #ffffff)",
|
|
15941
|
+
fontFamily: "var(--bw-font-family)",
|
|
15942
|
+
fontWeight: 700,
|
|
15943
|
+
cursor: canContinueToPayment ? "pointer" : "not-allowed",
|
|
15944
|
+
opacity: canContinueToPayment ? 1 : 0.6,
|
|
15945
|
+
}, children: t("button.continueToPayment") }))] }));
|
|
15946
|
+
if (!freeformConfig) {
|
|
15947
|
+
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") }) }));
|
|
15948
|
+
}
|
|
15949
|
+
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
|
|
15950
|
+
? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
|
|
15951
|
+
: 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 }))] })] })) }) }));
|
|
15952
|
+
}
|
|
15953
|
+
|
|
15392
15954
|
const getThemeConfig = (theme = "generic") => {
|
|
15393
15955
|
switch (theme) {
|
|
15394
15956
|
case "christmas":
|
|
@@ -16023,12 +16585,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16023
16585
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
16024
16586
|
const [successPaymentId, setSuccessPaymentId] = useState(null);
|
|
16025
16587
|
const [systemConfig, setSystemConfig] = useState(null);
|
|
16588
|
+
const [waitlistEnabled, setWaitlistEnabled] = useState(false);
|
|
16026
16589
|
// When true, loadEventInstances skips the single-instance auto-select (used by handleShowAllEvents)
|
|
16027
16590
|
const skipInstanceAutoSelectRef = useRef(false);
|
|
16028
16591
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
16029
16592
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
16030
16593
|
const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
|
|
16031
16594
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
|
|
16595
|
+
const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = useState(false);
|
|
16032
16596
|
// Google Ads config (received from API, set once from the first API response)
|
|
16033
16597
|
const [googleAdsConfig, setGoogleAdsConfig] = useState(null);
|
|
16034
16598
|
const extractGoogleAdsConfig = (data) => {
|
|
@@ -16042,6 +16606,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16042
16606
|
// Voucher purchase state
|
|
16043
16607
|
const [voucherConfig, setVoucherConfig] = useState(null);
|
|
16044
16608
|
const [voucherEventTypes, setVoucherEventTypes] = useState([]);
|
|
16609
|
+
const [voucherCategories, setVoucherCategories] = useState([]);
|
|
16045
16610
|
const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = useState(false);
|
|
16046
16611
|
const [isVoucherFormOpen, setIsVoucherFormOpen] = useState(false);
|
|
16047
16612
|
const [voucherPurchaseResult, setVoucherPurchaseResult] = useState(null);
|
|
@@ -16121,6 +16686,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16121
16686
|
setVoucherConfig(mergedConfig);
|
|
16122
16687
|
extractGoogleAdsConfig(data);
|
|
16123
16688
|
setVoucherEventTypes(data.eventTypes || []);
|
|
16689
|
+
setVoucherCategories(data.categories || []);
|
|
16124
16690
|
// Set system config for payment processing
|
|
16125
16691
|
if (data.paymentProvider) {
|
|
16126
16692
|
setSystemConfig({
|
|
@@ -16149,7 +16715,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16149
16715
|
if (!analyticsInitRef.current && config.organizationId) {
|
|
16150
16716
|
analyticsInitRef.current = true;
|
|
16151
16717
|
initAnalytics(config.apiBaseUrl, config.organizationId, {
|
|
16152
|
-
partnerContractId: config.partnerContractId,
|
|
16718
|
+
...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
|
|
16153
16719
|
});
|
|
16154
16720
|
trackEvent("widget_loaded", {
|
|
16155
16721
|
viewMode,
|
|
@@ -16513,6 +17079,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16513
17079
|
}
|
|
16514
17080
|
extractGoogleAdsConfig(data);
|
|
16515
17081
|
setEventInstances(data.eventInstances);
|
|
17082
|
+
setWaitlistEnabled(data.waitlistEnabled ?? false);
|
|
16516
17083
|
if (data.paymentProvider) {
|
|
16517
17084
|
setSystemConfig({
|
|
16518
17085
|
paymentProvider: data.paymentProvider,
|
|
@@ -16628,6 +17195,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16628
17195
|
const handleEventTypeSelect = async (eventType) => {
|
|
16629
17196
|
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16630
17197
|
setSelectedEventType(eventType);
|
|
17198
|
+
if (eventType.bookingMode === "freeform") {
|
|
17199
|
+
setCurrentStep("freeform");
|
|
17200
|
+
setShouldRenderFreeformSelection(true);
|
|
17201
|
+
setIsLoadingEventInstances(true);
|
|
17202
|
+
try {
|
|
17203
|
+
await loadEventInstances(eventType.id);
|
|
17204
|
+
}
|
|
17205
|
+
finally {
|
|
17206
|
+
setIsLoadingEventInstances(false);
|
|
17207
|
+
}
|
|
17208
|
+
return;
|
|
17209
|
+
}
|
|
16631
17210
|
setCurrentStep("eventInstances");
|
|
16632
17211
|
setShouldRenderInstanceSelection(true);
|
|
16633
17212
|
setIsLoadingEventInstances(true);
|
|
@@ -16718,6 +17297,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16718
17297
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16719
17298
|
setSidebarOpen(false);
|
|
16720
17299
|
setShouldRenderBookingForm(false);
|
|
17300
|
+
setShouldRenderFreeformSelection(false);
|
|
16721
17301
|
setBookingPersistedState(null);
|
|
16722
17302
|
config.onSuccess?.(result);
|
|
16723
17303
|
};
|
|
@@ -17033,6 +17613,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17033
17613
|
setShouldRenderInstanceSelection(false);
|
|
17034
17614
|
setShouldRenderUpsells(false);
|
|
17035
17615
|
setShouldRenderBookingForm(false);
|
|
17616
|
+
setShouldRenderFreeformSelection(false);
|
|
17036
17617
|
setSelectedUpsells([]);
|
|
17037
17618
|
setUpsells([]);
|
|
17038
17619
|
setBookingPersistedState(null);
|
|
@@ -17054,6 +17635,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17054
17635
|
setShouldRenderInstanceSelection(false);
|
|
17055
17636
|
setShouldRenderUpsells(false);
|
|
17056
17637
|
setShouldRenderBookingForm(false);
|
|
17638
|
+
setShouldRenderFreeformSelection(false);
|
|
17057
17639
|
setSelectedUpsells([]);
|
|
17058
17640
|
setUpsells([]);
|
|
17059
17641
|
setBookingPersistedState(null);
|
|
@@ -17066,13 +17648,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17066
17648
|
}, isOpen: currentStep === "eventInstances", onClose: () => {
|
|
17067
17649
|
setShowingPreview(true);
|
|
17068
17650
|
setCurrentStep("eventTypes");
|
|
17069
|
-
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17651
|
+
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17070
17652
|
setIsSuccess(false);
|
|
17071
17653
|
setCurrentStep("eventTypes");
|
|
17072
17654
|
setShowingPreview(true);
|
|
17073
17655
|
setSuccessPaymentId(null);
|
|
17074
17656
|
setShouldRenderInstanceSelection(false);
|
|
17075
17657
|
setShouldRenderBookingForm(false);
|
|
17658
|
+
setShouldRenderFreeformSelection(false);
|
|
17076
17659
|
const url = new URL(window.location.href);
|
|
17077
17660
|
url.searchParams.delete("payment_intent");
|
|
17078
17661
|
url.searchParams.delete("payment_intent_client_secret");
|
|
@@ -17116,13 +17699,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17116
17699
|
setCurrentStep("booking");
|
|
17117
17700
|
setShouldRenderBookingForm(true);
|
|
17118
17701
|
}
|
|
17702
|
+
else if (selectedEventType?.bookingMode === "freeform") {
|
|
17703
|
+
setCurrentStep("freeform");
|
|
17704
|
+
setShouldRenderFreeformSelection(true);
|
|
17705
|
+
}
|
|
17119
17706
|
else {
|
|
17120
17707
|
setCurrentStep("eventInstances");
|
|
17121
17708
|
setSidebarOpen(true);
|
|
17122
17709
|
setShouldRenderInstanceSelection(true);
|
|
17123
17710
|
}
|
|
17124
17711
|
}, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
|
|
17125
|
-
(isDirectInstanceMode
|
|
17712
|
+
(isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
|
|
17713
|
+
? t("button.bookNow")
|
|
17714
|
+
: 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, waitlistEnabled: waitlistEnabled })), 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: () => {
|
|
17715
|
+
setCurrentStep("eventTypes");
|
|
17716
|
+
setSidebarOpen(false);
|
|
17717
|
+
setShouldRenderFreeformSelection(false);
|
|
17718
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17126
17719
|
setIsSuccess(false);
|
|
17127
17720
|
setCurrentStep("eventTypes");
|
|
17128
17721
|
setSidebarOpen(false);
|
|
@@ -17130,6 +17723,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17130
17723
|
setShouldRenderInstanceSelection(false);
|
|
17131
17724
|
setShouldRenderUpsells(false);
|
|
17132
17725
|
setShouldRenderBookingForm(false);
|
|
17726
|
+
setShouldRenderFreeformSelection(false);
|
|
17133
17727
|
setSelectedUpsells([]);
|
|
17134
17728
|
setUpsells([]);
|
|
17135
17729
|
const url = new URL(window.location.href);
|
|
@@ -17142,7 +17736,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17142
17736
|
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
17143
17737
|
}
|
|
17144
17738
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
17145
|
-
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: {
|
|
17739
|
+
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, showImages: config.showEventImages !== false })), 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: {
|
|
17146
17740
|
display: "inline-block",
|
|
17147
17741
|
width: "32px",
|
|
17148
17742
|
height: "32px",
|
|
@@ -17183,13 +17777,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17183
17777
|
};
|
|
17184
17778
|
};
|
|
17185
17779
|
const backHandlers = getBackHandlers();
|
|
17186
|
-
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(
|
|
17780
|
+
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, waitlistEnabled: waitlistEnabled })), 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: () => {
|
|
17781
|
+
setCurrentStep("eventTypes");
|
|
17782
|
+
setShouldRenderFreeformSelection(false);
|
|
17783
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
|
|
17187
17784
|
setIsSuccess(false);
|
|
17188
17785
|
setCurrentStep("eventTypes");
|
|
17189
17786
|
setSuccessPaymentId(null);
|
|
17190
17787
|
setShouldRenderInstanceSelection(false);
|
|
17191
17788
|
setShouldRenderUpsells(false);
|
|
17192
17789
|
setShouldRenderBookingForm(false);
|
|
17790
|
+
setShouldRenderFreeformSelection(false);
|
|
17193
17791
|
setSelectedUpsells([]);
|
|
17194
17792
|
setUpsells([]);
|
|
17195
17793
|
const url = new URL(window.location.href);
|
|
@@ -17199,7 +17797,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17199
17797
|
url.searchParams.delete("mollie_payment_id");
|
|
17200
17798
|
url.searchParams.delete("mollie_status");
|
|
17201
17799
|
window.history.replaceState({}, "", url.toString());
|
|
17202
|
-
}, 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: () => {
|
|
17800
|
+
}, 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: () => {
|
|
17203
17801
|
setIsSuccess(false);
|
|
17204
17802
|
setVoucherPurchaseResult(null);
|
|
17205
17803
|
const url = new URL(window.location.href);
|
|
@@ -17247,7 +17845,7 @@ function styleInject(css, ref) {
|
|
|
17247
17845
|
}
|
|
17248
17846
|
}
|
|
17249
17847
|
|
|
17250
|
-
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}}";
|
|
17848
|
+
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}}";
|
|
17251
17849
|
styleInject(css_248z);
|
|
17252
17850
|
|
|
17253
17851
|
// Export init function for vanilla JS usage
|