@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.cjs
CHANGED
|
@@ -169,6 +169,10 @@ const de$1 = {
|
|
|
169
169
|
"booking.stepUpsells": "Extras",
|
|
170
170
|
"booking.stepDetails": "Angaben",
|
|
171
171
|
"booking.stepPayment": "Zahlung",
|
|
172
|
+
"booking.steps": "{{count}} Schritt(e)",
|
|
173
|
+
"booking.freeformUnitsAvailable": "{{count}} Einheiten verfuegbar",
|
|
174
|
+
"booking.freeformNotAvailable": "Das gewaehlte Zeitfenster ist nicht verfuegbar",
|
|
175
|
+
"booking.freeformMissingConfig": "Die Freeform-Konfiguration fuer diesen Event-Typ fehlt.",
|
|
172
176
|
// Booking summary
|
|
173
177
|
"summary.title": "Buchungszusammenfassung",
|
|
174
178
|
"summary.subtotal": "Zwischensumme:",
|
|
@@ -224,6 +228,13 @@ const de$1 = {
|
|
|
224
228
|
"voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
|
|
225
229
|
"voucher.eventType": "Erlebnisgutschein",
|
|
226
230
|
"voucher.eventTypeDesc": "Für ein bestimmtes Erlebnis",
|
|
231
|
+
"voucher.categoryType": "Kategoriegutschein",
|
|
232
|
+
"voucher.categoryTypeDesc": "Für ein beliebiges Erlebnis einer Kategorie",
|
|
233
|
+
"voucher.categoryVoucher": "Kategoriegutschein",
|
|
234
|
+
"voucher.selectCategory": "Kategorie wählen",
|
|
235
|
+
"voucher.categoryLabel": "Kategorie",
|
|
236
|
+
"voucher.selectCategoryPlaceholder": "Kategorie auswählen...",
|
|
237
|
+
"voucher.categoryAmount": "Gutscheinwert",
|
|
227
238
|
"voucher.selectAmount": "Betrag wählen",
|
|
228
239
|
"voucher.customAmount": "Oder eigenen Betrag eingeben",
|
|
229
240
|
"voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
|
|
@@ -477,6 +488,10 @@ const en = {
|
|
|
477
488
|
"booking.stepUpsells": "Extras",
|
|
478
489
|
"booking.stepDetails": "Details",
|
|
479
490
|
"booking.stepPayment": "Payment",
|
|
491
|
+
"booking.steps": "{{count}} step(s)",
|
|
492
|
+
"booking.freeformUnitsAvailable": "{{count}} units available",
|
|
493
|
+
"booking.freeformNotAvailable": "Selected window is not available",
|
|
494
|
+
"booking.freeformMissingConfig": "Missing freeform configuration for this event type.",
|
|
480
495
|
// Booking summary
|
|
481
496
|
"summary.title": "Booking Summary",
|
|
482
497
|
"summary.subtotal": "Subtotal:",
|
|
@@ -532,6 +547,13 @@ const en = {
|
|
|
532
547
|
"voucher.monetaryTypeDesc": "Choose any amount",
|
|
533
548
|
"voucher.eventType": "Event Voucher",
|
|
534
549
|
"voucher.eventTypeDesc": "For a specific experience",
|
|
550
|
+
"voucher.categoryType": "Category Voucher",
|
|
551
|
+
"voucher.categoryTypeDesc": "For any experience in a category",
|
|
552
|
+
"voucher.categoryVoucher": "Category Voucher",
|
|
553
|
+
"voucher.selectCategory": "Select Category",
|
|
554
|
+
"voucher.categoryLabel": "Category",
|
|
555
|
+
"voucher.selectCategoryPlaceholder": "Choose a category...",
|
|
556
|
+
"voucher.categoryAmount": "Voucher Value",
|
|
535
557
|
"voucher.selectAmount": "Select Amount",
|
|
536
558
|
"voucher.customAmount": "Or enter custom amount",
|
|
537
559
|
"voucher.amountRange": "Amount must be between {{min}} and {{max}}",
|
|
@@ -719,6 +741,21 @@ const es = {
|
|
|
719
741
|
"instances.bestPrice": "¡¡¡mejor precio!!!",
|
|
720
742
|
"instances.goodPrice": "buen precio",
|
|
721
743
|
"instances.oclock": "",
|
|
744
|
+
// Lista de espera
|
|
745
|
+
"waitlist.joinCta": "Lista de espera",
|
|
746
|
+
"waitlist.title": "Apuntarse a la lista de espera",
|
|
747
|
+
"waitlist.subtitle": "{{name}} está agotado. Deja tus datos y te contactaremos si se libera un lugar.",
|
|
748
|
+
"waitlist.name": "Nombre",
|
|
749
|
+
"waitlist.email": "Correo electrónico",
|
|
750
|
+
"waitlist.phone": "Teléfono (opcional)",
|
|
751
|
+
"waitlist.requiredFields": "Por favor, introduce tu nombre y correo electrónico.",
|
|
752
|
+
"waitlist.join": "Apuntarse",
|
|
753
|
+
"waitlist.submitting": "Apuntando...",
|
|
754
|
+
"waitlist.cancel": "Cancelar",
|
|
755
|
+
"waitlist.submitError": "Algo salió mal. Por favor, inténtalo de nuevo.",
|
|
756
|
+
"waitlist.successTitle": "¡Estás en la lista!",
|
|
757
|
+
"waitlist.successMessage": "Eres el número {{position}} en la lista de espera. Te contactaremos si se libera un lugar.",
|
|
758
|
+
"waitlist.done": "Listo",
|
|
722
759
|
// Next events preview
|
|
723
760
|
"nextEvents.title": "Próximas fechas disponibles",
|
|
724
761
|
"nextEvents.subtitle": "Selecciona una fecha o consulta todas las fechas disponibles",
|
|
@@ -822,6 +859,13 @@ const es = {
|
|
|
822
859
|
"voucher.monetaryTypeDesc": "Elige cualquier cantidad",
|
|
823
860
|
"voucher.eventType": "Vale de experiencia",
|
|
824
861
|
"voucher.eventTypeDesc": "Para una experiencia específica",
|
|
862
|
+
"voucher.categoryType": "Vale de categoría",
|
|
863
|
+
"voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
|
|
864
|
+
"voucher.categoryVoucher": "Vale de categoría",
|
|
865
|
+
"voucher.selectCategory": "Seleccionar categoría",
|
|
866
|
+
"voucher.categoryLabel": "Categoría",
|
|
867
|
+
"voucher.selectCategoryPlaceholder": "Elige una categoría...",
|
|
868
|
+
"voucher.categoryAmount": "Valor del vale",
|
|
825
869
|
"voucher.selectAmount": "Selecciona el importe",
|
|
826
870
|
"voucher.customAmount": "O introduce un importe personalizado",
|
|
827
871
|
"voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
|
|
@@ -1009,6 +1053,21 @@ const pt = {
|
|
|
1009
1053
|
"instances.bestPrice": "melhor preço !!!",
|
|
1010
1054
|
"instances.goodPrice": "bom preço",
|
|
1011
1055
|
"instances.oclock": "",
|
|
1056
|
+
// Lista de espera
|
|
1057
|
+
"waitlist.joinCta": "Lista de espera",
|
|
1058
|
+
"waitlist.title": "Entrar na lista de espera",
|
|
1059
|
+
"waitlist.subtitle": "{{name}} está esgotado. Deixe os seus dados e entraremos em contacto se surgir uma vaga.",
|
|
1060
|
+
"waitlist.name": "Nome",
|
|
1061
|
+
"waitlist.email": "E-mail",
|
|
1062
|
+
"waitlist.phone": "Telefone (opcional)",
|
|
1063
|
+
"waitlist.requiredFields": "Por favor, introduza o seu nome e e-mail.",
|
|
1064
|
+
"waitlist.join": "Inscrever-se",
|
|
1065
|
+
"waitlist.submitting": "A inscrever...",
|
|
1066
|
+
"waitlist.cancel": "Cancelar",
|
|
1067
|
+
"waitlist.submitError": "Algo correu mal. Por favor, tente novamente.",
|
|
1068
|
+
"waitlist.successTitle": "Está na lista!",
|
|
1069
|
+
"waitlist.successMessage": "É o número {{position}} na lista de espera. Entraremos em contacto se surgir uma vaga.",
|
|
1070
|
+
"waitlist.done": "Concluído",
|
|
1012
1071
|
// Next events preview
|
|
1013
1072
|
"nextEvents.title": "Próximas datas disponíveis",
|
|
1014
1073
|
"nextEvents.subtitle": "Selecione uma data ou veja todas as datas disponíveis",
|
|
@@ -1112,6 +1171,13 @@ const pt = {
|
|
|
1112
1171
|
"voucher.monetaryTypeDesc": "Escolha qualquer quantia",
|
|
1113
1172
|
"voucher.eventType": "Vale de experiência",
|
|
1114
1173
|
"voucher.eventTypeDesc": "Para uma experiência específica",
|
|
1174
|
+
"voucher.categoryType": "Vale de categoria",
|
|
1175
|
+
"voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
|
|
1176
|
+
"voucher.categoryVoucher": "Vale de categoria",
|
|
1177
|
+
"voucher.selectCategory": "Selecionar categoria",
|
|
1178
|
+
"voucher.categoryLabel": "Categoria",
|
|
1179
|
+
"voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
|
|
1180
|
+
"voucher.categoryAmount": "Valor do vale",
|
|
1115
1181
|
"voucher.selectAmount": "Selecione o valor",
|
|
1116
1182
|
"voucher.customAmount": "Ou introduza um valor personalizado",
|
|
1117
1183
|
"voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
|
|
@@ -1299,6 +1365,21 @@ const sv = {
|
|
|
1299
1365
|
"instances.bestPrice": "bästa pris !!!",
|
|
1300
1366
|
"instances.goodPrice": "bra pris",
|
|
1301
1367
|
"instances.oclock": "",
|
|
1368
|
+
// Väntelista
|
|
1369
|
+
"waitlist.joinCta": "Ställ dig i kö",
|
|
1370
|
+
"waitlist.title": "Ställ dig i kö",
|
|
1371
|
+
"waitlist.subtitle": "{{name}} är fullbokat. Lämna dina uppgifter så kontaktar vi dig om en plats blir ledig.",
|
|
1372
|
+
"waitlist.name": "Namn",
|
|
1373
|
+
"waitlist.email": "E-post",
|
|
1374
|
+
"waitlist.phone": "Telefon (valfritt)",
|
|
1375
|
+
"waitlist.requiredFields": "Ange ditt namn och din e-postadress.",
|
|
1376
|
+
"waitlist.join": "Ställ dig i kö",
|
|
1377
|
+
"waitlist.submitting": "Anmäler...",
|
|
1378
|
+
"waitlist.cancel": "Avbryt",
|
|
1379
|
+
"waitlist.submitError": "Något gick fel. Försök igen.",
|
|
1380
|
+
"waitlist.successTitle": "Du står i kön!",
|
|
1381
|
+
"waitlist.successMessage": "Du är nummer {{position}} på väntelistan. Vi hör av oss om en plats blir ledig.",
|
|
1382
|
+
"waitlist.done": "Klar",
|
|
1302
1383
|
// Next events preview
|
|
1303
1384
|
"nextEvents.title": "Nästa tillgängliga datum",
|
|
1304
1385
|
"nextEvents.subtitle": "Välj ett datum eller visa alla tillgängliga datum",
|
|
@@ -1402,6 +1483,13 @@ const sv = {
|
|
|
1402
1483
|
"voucher.monetaryTypeDesc": "Välj valfritt belopp",
|
|
1403
1484
|
"voucher.eventType": "Upplevelsebevis",
|
|
1404
1485
|
"voucher.eventTypeDesc": "För en specifik upplevelse",
|
|
1486
|
+
"voucher.categoryType": "Kategoripresent",
|
|
1487
|
+
"voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
|
|
1488
|
+
"voucher.categoryVoucher": "Kategoripresent",
|
|
1489
|
+
"voucher.selectCategory": "Välj kategori",
|
|
1490
|
+
"voucher.categoryLabel": "Kategori",
|
|
1491
|
+
"voucher.selectCategoryPlaceholder": "Välj en kategori...",
|
|
1492
|
+
"voucher.categoryAmount": "Presentkortsvärde",
|
|
1405
1493
|
"voucher.selectAmount": "Välj belopp",
|
|
1406
1494
|
"voucher.customAmount": "Eller ange eget belopp",
|
|
1407
1495
|
"voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
|
|
@@ -6372,7 +6460,7 @@ function loadMollieScript() {
|
|
|
6372
6460
|
document.head.appendChild(script);
|
|
6373
6461
|
});
|
|
6374
6462
|
}
|
|
6375
|
-
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
|
|
6463
|
+
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
|
|
6376
6464
|
const t = useTranslations();
|
|
6377
6465
|
const { locale } = useLocale();
|
|
6378
6466
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
@@ -6386,7 +6474,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6386
6474
|
const cardFormRef = React.useRef(null);
|
|
6387
6475
|
const cardContainerRef = React.useRef(null);
|
|
6388
6476
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6389
|
-
const isFullyCoveredByGiftCards =
|
|
6477
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
6478
|
+
? false
|
|
6479
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
6390
6480
|
const isCreditCard = selectedMethod === "creditcard";
|
|
6391
6481
|
React.useEffect(() => {
|
|
6392
6482
|
if (isFullyCoveredByGiftCards)
|
|
@@ -6496,6 +6586,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6496
6586
|
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6497
6587
|
}
|
|
6498
6588
|
const validateBeforePayment = () => {
|
|
6589
|
+
if (freeformSelection) {
|
|
6590
|
+
return null;
|
|
6591
|
+
}
|
|
6499
6592
|
const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
|
|
6500
6593
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6501
6594
|
if (participantCount > availableSpots) {
|
|
@@ -6507,21 +6600,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6507
6600
|
return null;
|
|
6508
6601
|
};
|
|
6509
6602
|
const buildRequestData = (cardToken) => ({
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6603
|
+
...(freeformSelection
|
|
6604
|
+
? {
|
|
6605
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
6606
|
+
organizationId: config.organizationId,
|
|
6607
|
+
start: freeformSelection.start,
|
|
6608
|
+
end: freeformSelection.end,
|
|
6609
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
6610
|
+
amount: Math.round(totalAmount),
|
|
6611
|
+
currency: "EUR",
|
|
6612
|
+
customerName: freeformSelection.customerName.trim(),
|
|
6613
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
6614
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
6615
|
+
comment: freeformSelection.comment?.trim(),
|
|
6616
|
+
locale,
|
|
6617
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6618
|
+
...(cardToken && { cardToken }),
|
|
6619
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6620
|
+
}
|
|
6621
|
+
: {
|
|
6622
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
6623
|
+
organizationId: config.organizationId,
|
|
6624
|
+
amount: Math.round(totalAmount),
|
|
6625
|
+
currency: "EUR",
|
|
6626
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
6627
|
+
discountCode: discountCode?.code,
|
|
6628
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
6629
|
+
customerName: formData.customerName?.trim(),
|
|
6630
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
6631
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
6632
|
+
comment: formData.comment?.trim(),
|
|
6633
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6634
|
+
...(cardToken && { cardToken }),
|
|
6635
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6636
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
6637
|
+
}),
|
|
6525
6638
|
});
|
|
6526
6639
|
const handleCardPayment = async () => {
|
|
6527
6640
|
if (!mollieRef.current)
|
|
@@ -6539,7 +6652,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6539
6652
|
setPaymentError(tokenError?.message || t("error.createPayment"));
|
|
6540
6653
|
return;
|
|
6541
6654
|
}
|
|
6542
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6655
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6656
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6657
|
+
: "/booking/create-mollie-payment"), {
|
|
6543
6658
|
method: "POST",
|
|
6544
6659
|
headers: createApiHeaders(config, locale),
|
|
6545
6660
|
body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
|
|
@@ -6575,7 +6690,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6575
6690
|
setPaymentError(validationError);
|
|
6576
6691
|
return;
|
|
6577
6692
|
}
|
|
6578
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6693
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6694
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6695
|
+
: "/booking/create-mollie-payment"), {
|
|
6579
6696
|
method: "POST",
|
|
6580
6697
|
headers: createApiHeaders(config, locale),
|
|
6581
6698
|
body: JSON.stringify(createRequestBody(config, buildRequestData())),
|
|
@@ -6603,7 +6720,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6603
6720
|
? t("button.processingPayment")
|
|
6604
6721
|
: selectedMethodData
|
|
6605
6722
|
? t("payment.payWithMethod", { method: selectedMethodData.description })
|
|
6606
|
-
: totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6723
|
+
: !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6607
6724
|
? t("button.depositAndBook")
|
|
6608
6725
|
: t("button.bookNow");
|
|
6609
6726
|
const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
|
|
@@ -6910,12 +7027,18 @@ var loadStripe = function loadStripe() {
|
|
|
6910
7027
|
};
|
|
6911
7028
|
|
|
6912
7029
|
// Inner component that uses the Stripe hooks
|
|
6913
|
-
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
|
|
7030
|
+
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
|
|
6914
7031
|
const t = useTranslations();
|
|
6915
|
-
const
|
|
6916
|
-
const
|
|
6917
|
-
|
|
7032
|
+
const isFreeform = Boolean(freeformSelection);
|
|
7033
|
+
const participantCount = isFreeform
|
|
7034
|
+
? 1
|
|
7035
|
+
: formData.participants.filter((p) => p.name.trim()).length;
|
|
7036
|
+
const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
|
|
7037
|
+
const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
|
|
6918
7038
|
return (jsxRuntime.jsx(StripeElementsPaymentForm, { onValidate: () => {
|
|
7039
|
+
if (isFreeform) {
|
|
7040
|
+
return null;
|
|
7041
|
+
}
|
|
6919
7042
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6920
7043
|
if (participantCount > availableSpots) {
|
|
6921
7044
|
return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
|
|
@@ -6937,7 +7060,7 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
|
|
|
6937
7060
|
},
|
|
6938
7061
|
}, submitContent: jsxRuntime.jsx(jsxRuntime.Fragment, { children: submitLabel }), loadingContent: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
|
|
6939
7062
|
}
|
|
6940
|
-
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
|
|
7063
|
+
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
|
|
6941
7064
|
const t = useTranslations();
|
|
6942
7065
|
const { locale } = useLocale();
|
|
6943
7066
|
const [clientSecret, setClientSecret] = React.useState(null);
|
|
@@ -6957,7 +7080,9 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6957
7080
|
return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
|
|
6958
7081
|
}, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
|
|
6959
7082
|
const storageKey = typeof window !== "undefined"
|
|
6960
|
-
?
|
|
7083
|
+
? freeformSelection
|
|
7084
|
+
? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
|
|
7085
|
+
: `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
|
|
6961
7086
|
: "";
|
|
6962
7087
|
const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
|
|
6963
7088
|
function loadPersistedPaymentIntent() {
|
|
@@ -7012,48 +7137,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7012
7137
|
}, [paymentIntentId]);
|
|
7013
7138
|
React.useEffect(() => {
|
|
7014
7139
|
const createStripePayment = async () => {
|
|
7015
|
-
if (!systemConfig || !eventDetails
|
|
7140
|
+
if (!systemConfig || !eventDetails) {
|
|
7141
|
+
return;
|
|
7142
|
+
}
|
|
7143
|
+
if (freeformSelection) {
|
|
7144
|
+
if (!freeformSelection.customerEmail?.trim() ||
|
|
7145
|
+
!freeformSelection.customerName?.trim() ||
|
|
7146
|
+
!freeformSelection.start ||
|
|
7147
|
+
!freeformSelection.end) {
|
|
7148
|
+
return;
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
7151
|
+
else if (!formData.participants?.length) {
|
|
7016
7152
|
return;
|
|
7017
7153
|
}
|
|
7018
7154
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
7019
|
-
if (
|
|
7155
|
+
if (!freeformSelection &&
|
|
7156
|
+
(participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
|
|
7020
7157
|
return;
|
|
7021
7158
|
}
|
|
7022
7159
|
setIsCreatingPaymentIntent(true);
|
|
7023
7160
|
setPaymentError(null);
|
|
7024
7161
|
try {
|
|
7025
|
-
const
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7162
|
+
const endpoint = freeformSelection
|
|
7163
|
+
? "/booking/create-freeform-stripe-payment"
|
|
7164
|
+
: "/booking/create-stripe-payment";
|
|
7165
|
+
const requestData = freeformSelection
|
|
7166
|
+
? {
|
|
7167
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
7168
|
+
organizationId: config.organizationId,
|
|
7169
|
+
start: freeformSelection.start,
|
|
7170
|
+
end: freeformSelection.end,
|
|
7171
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
7172
|
+
amount: Math.round(totalAmount),
|
|
7173
|
+
currency: "eur",
|
|
7174
|
+
customerName: freeformSelection.customerName.trim(),
|
|
7175
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
7176
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
7177
|
+
comment: freeformSelection.comment?.trim(),
|
|
7178
|
+
locale,
|
|
7179
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7180
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7181
|
+
}
|
|
7182
|
+
: {
|
|
7183
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
7184
|
+
organizationId: config.organizationId,
|
|
7185
|
+
amount: Math.round(totalAmount),
|
|
7186
|
+
currency: "eur",
|
|
7187
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
7188
|
+
discountCode: discountCode?.code,
|
|
7189
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
7190
|
+
customerName: formData.customerName?.trim(),
|
|
7191
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
7192
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
7193
|
+
comment: formData.comment?.trim(),
|
|
7194
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7195
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7196
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
7197
|
+
};
|
|
7044
7198
|
if (!requestData.organizationId) {
|
|
7045
7199
|
throw new Error("Organization ID is required");
|
|
7046
7200
|
}
|
|
7047
|
-
if (
|
|
7201
|
+
if (requestData.amount === undefined || requestData.amount < 0) {
|
|
7048
7202
|
throw new Error("Valid amount is required");
|
|
7049
7203
|
}
|
|
7050
|
-
if (!requestData
|
|
7204
|
+
if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
|
|
7205
|
+
throw new Error("Event instance ID is required");
|
|
7206
|
+
}
|
|
7207
|
+
if (!freeformSelection &&
|
|
7208
|
+
"participants" in requestData &&
|
|
7209
|
+
(!requestData.participants || requestData.participants.length === 0)) {
|
|
7051
7210
|
throw new Error("At least one participant is required");
|
|
7052
7211
|
}
|
|
7053
7212
|
if (!requestData.customerEmail) {
|
|
7054
7213
|
throw new Error("Customer email is required");
|
|
7055
7214
|
}
|
|
7056
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
7215
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
|
|
7057
7216
|
method: "POST",
|
|
7058
7217
|
headers: createApiHeaders(config, locale),
|
|
7059
7218
|
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
@@ -7098,9 +7257,13 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7098
7257
|
giftCards,
|
|
7099
7258
|
config,
|
|
7100
7259
|
upsellSelections,
|
|
7260
|
+
freeformSelection,
|
|
7261
|
+
locale,
|
|
7101
7262
|
]);
|
|
7102
7263
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
7103
|
-
const isFullyCoveredByGiftCards =
|
|
7264
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
7265
|
+
? false
|
|
7266
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
7104
7267
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
7105
7268
|
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
7106
7269
|
}
|
|
@@ -7139,7 +7302,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7139
7302
|
setPaymentIntentId(null);
|
|
7140
7303
|
setClientSecret(null);
|
|
7141
7304
|
onSuccess(result);
|
|
7142
|
-
}, onError: onError }) }));
|
|
7305
|
+
}, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
|
|
7143
7306
|
}
|
|
7144
7307
|
|
|
7145
7308
|
const HOLD_DURATION_SECONDS = 15 * 60;
|
|
@@ -13399,20 +13562,22 @@ const sectionHeaderStyles = sectionStyles.header;
|
|
|
13399
13562
|
const labelStyles = formStyles.label;
|
|
13400
13563
|
const inputStyles = formStyles.input;
|
|
13401
13564
|
const errorTextStyles = formStyles.error;
|
|
13402
|
-
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13565
|
+
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13403
13566
|
const t = useTranslations();
|
|
13404
13567
|
const { locale } = useLocale();
|
|
13405
13568
|
const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
|
|
13406
13569
|
// Form state
|
|
13407
|
-
// Show voucher type selection if both monetary and event vouchers are allowed
|
|
13408
|
-
// The event type selection will handle the case when no event types are available
|
|
13409
13570
|
const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
|
|
13410
13571
|
const canUseEventVoucherType = voucherConfig.allowEventVouchers;
|
|
13411
|
-
const
|
|
13572
|
+
const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
|
|
13573
|
+
const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
|
|
13574
|
+
const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
|
|
13412
13575
|
const [voucherType, setVoucherType] = React.useState(canUseMonetaryVoucherType ? "monetary" : "event");
|
|
13413
13576
|
const [monetaryAmount, setMonetaryAmount] = React.useState(voucherConfig.monetaryPresets[2] || 10000);
|
|
13414
13577
|
const [selectedEventTypeId, setSelectedEventTypeId] = React.useState(null);
|
|
13415
13578
|
const [eventQuantity, setEventQuantity] = React.useState(1);
|
|
13579
|
+
const [selectedCategoryId, setSelectedCategoryId] = React.useState(null);
|
|
13580
|
+
const [categoryAmount, setCategoryAmount] = React.useState(10000);
|
|
13416
13581
|
const [recipientName, setRecipientName] = React.useState("");
|
|
13417
13582
|
const [message, setMessage] = React.useState("");
|
|
13418
13583
|
const [customerName, setCustomerName] = React.useState("");
|
|
@@ -13472,6 +13637,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13472
13637
|
if (voucherType === "monetary") {
|
|
13473
13638
|
return monetaryAmount;
|
|
13474
13639
|
}
|
|
13640
|
+
else if (voucherType === "category") {
|
|
13641
|
+
return categoryAmount;
|
|
13642
|
+
}
|
|
13475
13643
|
else {
|
|
13476
13644
|
const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13477
13645
|
if (eventType) {
|
|
@@ -13479,7 +13647,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13479
13647
|
}
|
|
13480
13648
|
return 0;
|
|
13481
13649
|
}
|
|
13482
|
-
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
|
|
13650
|
+
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
|
|
13483
13651
|
const selectedEventType = React.useMemo(() => {
|
|
13484
13652
|
return eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13485
13653
|
}, [eventTypes, selectedEventTypeId]);
|
|
@@ -13488,6 +13656,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13488
13656
|
voucherType,
|
|
13489
13657
|
selectedEventTypeId,
|
|
13490
13658
|
eventQuantity,
|
|
13659
|
+
selectedCategoryId,
|
|
13660
|
+
categoryAmount,
|
|
13491
13661
|
recipientName: recipientName.trim(),
|
|
13492
13662
|
message: message.trim(),
|
|
13493
13663
|
customerName: customerName.trim(),
|
|
@@ -13497,6 +13667,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13497
13667
|
voucherType,
|
|
13498
13668
|
selectedEventTypeId,
|
|
13499
13669
|
eventQuantity,
|
|
13670
|
+
selectedCategoryId,
|
|
13671
|
+
categoryAmount,
|
|
13500
13672
|
recipientName,
|
|
13501
13673
|
message,
|
|
13502
13674
|
customerName,
|
|
@@ -13515,24 +13687,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13515
13687
|
return false;
|
|
13516
13688
|
if (voucherType === "event" && !selectedEventTypeId)
|
|
13517
13689
|
return false;
|
|
13690
|
+
if (voucherType === "category" && !selectedCategoryId)
|
|
13691
|
+
return false;
|
|
13518
13692
|
return true;
|
|
13519
|
-
}, [customerName, customerEmail, acceptTerms, totalAmount,
|
|
13693
|
+
}, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
|
|
13520
13694
|
const defaultMonetaryAmount = React.useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
|
|
13521
13695
|
const handleVoucherTypeChange = React.useCallback((nextType) => {
|
|
13522
13696
|
setVoucherType(nextType);
|
|
13523
13697
|
setPaymentError(null);
|
|
13524
13698
|
if (nextType === "event") {
|
|
13525
|
-
// Keep voucher type mutually exclusive: entering event flow resets amount selection state.
|
|
13526
13699
|
setMonetaryAmount(defaultMonetaryAmount);
|
|
13700
|
+
setSelectedCategoryId(null);
|
|
13701
|
+
setCategoryAmount(10000);
|
|
13527
13702
|
if (eventTypes.length === 1) {
|
|
13528
13703
|
setSelectedEventTypeId(eventTypes[0]?.id ?? null);
|
|
13529
13704
|
}
|
|
13530
13705
|
return;
|
|
13531
13706
|
}
|
|
13532
|
-
|
|
13707
|
+
if (nextType === "category") {
|
|
13708
|
+
setMonetaryAmount(defaultMonetaryAmount);
|
|
13709
|
+
setSelectedEventTypeId(null);
|
|
13710
|
+
setEventQuantity(1);
|
|
13711
|
+
if (categories.length === 1) {
|
|
13712
|
+
setSelectedCategoryId(categories[0]?.id ?? null);
|
|
13713
|
+
}
|
|
13714
|
+
return;
|
|
13715
|
+
}
|
|
13533
13716
|
setSelectedEventTypeId(null);
|
|
13534
13717
|
setEventQuantity(1);
|
|
13535
|
-
|
|
13718
|
+
setSelectedCategoryId(null);
|
|
13719
|
+
setCategoryAmount(10000);
|
|
13720
|
+
}, [defaultMonetaryAmount, eventTypes, categories]);
|
|
13536
13721
|
React.useEffect(() => {
|
|
13537
13722
|
if (!canUseMonetaryVoucherType && voucherType === "monetary") {
|
|
13538
13723
|
setVoucherType("event");
|
|
@@ -13573,9 +13758,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13573
13758
|
organizationId: config.organizationId,
|
|
13574
13759
|
amount: totalAmount,
|
|
13575
13760
|
currency: "eur",
|
|
13576
|
-
voucherType,
|
|
13577
|
-
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13761
|
+
voucherType: voucherType === "category" ? "monetary" : voucherType,
|
|
13762
|
+
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13763
|
+
: voucherType === "category" ? categoryAmount
|
|
13764
|
+
: undefined,
|
|
13578
13765
|
eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
|
|
13766
|
+
eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
|
|
13579
13767
|
eventAmount: voucherType === "event" ? eventQuantity : undefined,
|
|
13580
13768
|
recipientName: recipientName.trim() || undefined,
|
|
13581
13769
|
message: message.trim() || undefined,
|
|
@@ -13766,7 +13954,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13766
13954
|
fontSize: "13px",
|
|
13767
13955
|
color: "var(--bw-text-muted)",
|
|
13768
13956
|
marginTop: "4px",
|
|
13769
|
-
}, children: t("voucher.eventTypeDesc") })] }))
|
|
13957
|
+
}, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxRuntime.jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
|
|
13958
|
+
flex: 1,
|
|
13959
|
+
padding: "16px",
|
|
13960
|
+
borderRadius: "var(--bw-border-radius)",
|
|
13961
|
+
border: voucherType === "category"
|
|
13962
|
+
? "2px solid var(--bw-highlight-color)"
|
|
13963
|
+
: "1px solid var(--bw-border-color)",
|
|
13964
|
+
backgroundColor: voucherType === "category"
|
|
13965
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
13966
|
+
: "var(--bw-surface-color)",
|
|
13967
|
+
cursor: "pointer",
|
|
13968
|
+
fontFamily: "var(--bw-font-family)",
|
|
13969
|
+
transition: "all 0.2s ease",
|
|
13970
|
+
textAlign: "center",
|
|
13971
|
+
}, children: [jsxRuntime.jsx("div", { style: {
|
|
13972
|
+
fontWeight: 600,
|
|
13973
|
+
color: voucherType === "category"
|
|
13974
|
+
? "var(--bw-highlight-color)"
|
|
13975
|
+
: "var(--bw-text-color)",
|
|
13976
|
+
}, children: t("voucher.categoryType") }), jsxRuntime.jsx("div", { style: {
|
|
13977
|
+
fontSize: "13px",
|
|
13978
|
+
color: "var(--bw-text-muted)",
|
|
13979
|
+
marginTop: "4px",
|
|
13980
|
+
}, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsxRuntime.jsx("div", { style: {
|
|
13770
13981
|
display: "grid",
|
|
13771
13982
|
gridTemplateColumns: "repeat(3, 1fr)",
|
|
13772
13983
|
gap: "8px",
|
|
@@ -13821,7 +14032,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13821
14032
|
fontSize: "18px",
|
|
13822
14033
|
color: "var(--bw-highlight-color)",
|
|
13823
14034
|
fontFamily: "var(--bw-font-family)",
|
|
13824
|
-
}, children: formatCurrency(totalAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.
|
|
14035
|
+
}, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), jsxRuntime.jsx("div", { style: { marginTop: "12px" }, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), jsxRuntime.jsxs("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
|
|
14036
|
+
...inputStyles,
|
|
14037
|
+
cursor: categories.length === 1 ? "not-allowed" : "pointer",
|
|
14038
|
+
}, children: [categories.length > 1 && (jsxRuntime.jsx("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), jsxRuntime.jsx("div", { style: {
|
|
14039
|
+
display: "grid",
|
|
14040
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
14041
|
+
gap: "8px",
|
|
14042
|
+
marginTop: "4px",
|
|
14043
|
+
}, children: voucherConfig.monetaryPresets.map((preset) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
|
|
14044
|
+
padding: "10px",
|
|
14045
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14046
|
+
border: categoryAmount === preset
|
|
14047
|
+
? "2px solid var(--bw-highlight-color)"
|
|
14048
|
+
: "1px solid var(--bw-border-color)",
|
|
14049
|
+
backgroundColor: categoryAmount === preset
|
|
14050
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
14051
|
+
: "var(--bw-surface-color)",
|
|
14052
|
+
cursor: "pointer",
|
|
14053
|
+
fontFamily: "var(--bw-font-family)",
|
|
14054
|
+
fontWeight: 600,
|
|
14055
|
+
fontSize: "14px",
|
|
14056
|
+
color: categoryAmount === preset
|
|
14057
|
+
? "var(--bw-highlight-color)"
|
|
14058
|
+
: "var(--bw-text-color)",
|
|
14059
|
+
transition: "all 0.2s ease",
|
|
14060
|
+
}, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsxRuntime.jsx("div", { style: {
|
|
14061
|
+
marginTop: "16px",
|
|
14062
|
+
padding: "12px",
|
|
14063
|
+
backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
|
|
14064
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14065
|
+
border: "1px solid var(--bw-highlight-color)",
|
|
14066
|
+
}, children: jsxRuntime.jsxs("div", { style: {
|
|
14067
|
+
display: "flex",
|
|
14068
|
+
justifyContent: "space-between",
|
|
14069
|
+
alignItems: "center",
|
|
14070
|
+
}, children: [jsxRuntime.jsx("span", { style: {
|
|
14071
|
+
color: "var(--bw-text-color)",
|
|
14072
|
+
fontFamily: "var(--bw-font-family)",
|
|
14073
|
+
}, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsxRuntime.jsx("span", { style: {
|
|
14074
|
+
fontWeight: 700,
|
|
14075
|
+
fontSize: "18px",
|
|
14076
|
+
color: "var(--bw-highlight-color)",
|
|
14077
|
+
fontFamily: "var(--bw-font-family)",
|
|
14078
|
+
}, children: formatCurrency(categoryAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsxRuntime.jsx("p", { style: {
|
|
13825
14079
|
fontSize: "13px",
|
|
13826
14080
|
color: "var(--bw-text-muted)",
|
|
13827
14081
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -13854,7 +14108,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13854
14108
|
fontFamily: "var(--bw-font-family)",
|
|
13855
14109
|
}, children: voucherType === "monetary"
|
|
13856
14110
|
? t("voucher.monetaryVoucher")
|
|
13857
|
-
:
|
|
14111
|
+
: voucherType === "category"
|
|
14112
|
+
? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
|
|
14113
|
+
: `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxRuntime.jsxs("div", { style: {
|
|
13858
14114
|
fontSize: "13px",
|
|
13859
14115
|
color: "var(--bw-text-muted)",
|
|
13860
14116
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -14130,7 +14386,7 @@ function VoucherAttachment({ onClick }) {
|
|
|
14130
14386
|
}, children: t("voucher.buyAsGift") })] }));
|
|
14131
14387
|
}
|
|
14132
14388
|
|
|
14133
|
-
function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14389
|
+
function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14134
14390
|
if (!voucherConfig?.enabled) {
|
|
14135
14391
|
return null;
|
|
14136
14392
|
}
|
|
@@ -14141,7 +14397,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
|
|
|
14141
14397
|
justifyContent: "center",
|
|
14142
14398
|
}, children: jsxRuntime.jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
|
|
14143
14399
|
? Math.min(...eventTypes.map((et) => et.maxPrice))
|
|
14144
|
-
: undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
|
|
14400
|
+
: undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, categories: categories, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
|
|
14145
14401
|
}
|
|
14146
14402
|
|
|
14147
14403
|
// Helper function to preprocess markdown for underline support
|
|
@@ -14201,7 +14457,7 @@ function InfoBadge({ text }) {
|
|
|
14201
14457
|
pointerEvents: "none",
|
|
14202
14458
|
}, children: text }))] }));
|
|
14203
14459
|
}
|
|
14204
|
-
function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
|
|
14460
|
+
function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, showImages = true, }) {
|
|
14205
14461
|
const t = useTranslations();
|
|
14206
14462
|
const { locale } = useLocale();
|
|
14207
14463
|
const timezone = useTimezone();
|
|
@@ -14280,6 +14536,18 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14280
14536
|
gridAutoRows: "1fr",
|
|
14281
14537
|
}, children: eventTypes.map((eventType) => {
|
|
14282
14538
|
const isAvailable = eventType.hasAvailableInstances;
|
|
14539
|
+
const hasImage = showImages && eventType.images && eventType.images.length > 0;
|
|
14540
|
+
const availabilityBadge = (jsxRuntime.jsx("div", { style: {
|
|
14541
|
+
padding: "4px 8px",
|
|
14542
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
14543
|
+
fontSize: "12px",
|
|
14544
|
+
fontWeight: 600,
|
|
14545
|
+
color: "#ffffff",
|
|
14546
|
+
fontFamily: "var(--bw-font-family)",
|
|
14547
|
+
backgroundColor: isAvailable
|
|
14548
|
+
? "var(--bw-success-color)"
|
|
14549
|
+
: "var(--bw-error-color)",
|
|
14550
|
+
}, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }));
|
|
14283
14551
|
return (jsxRuntime.jsxs("div", { className: "event-type-card", style: {
|
|
14284
14552
|
position: "relative",
|
|
14285
14553
|
backgroundColor: "var(--bw-surface-color)",
|
|
@@ -14291,17 +14559,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14291
14559
|
fontFamily: "var(--bw-font-family)",
|
|
14292
14560
|
cursor: isAvailable ? "pointer" : "not-allowed",
|
|
14293
14561
|
opacity: isAvailable ? 1 : 0.6,
|
|
14294
|
-
}, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: jsxRuntime.jsx("div", { style: {
|
|
14295
|
-
padding: "4px 8px",
|
|
14296
|
-
borderRadius: "var(--bw-border-radius-small)",
|
|
14297
|
-
fontSize: "12px",
|
|
14298
|
-
fontWeight: 600,
|
|
14299
|
-
color: "#ffffff",
|
|
14300
|
-
fontFamily: "var(--bw-font-family)",
|
|
14301
|
-
backgroundColor: isAvailable
|
|
14302
|
-
? "var(--bw-success-color)"
|
|
14303
|
-
: "var(--bw-error-color)",
|
|
14304
|
-
}, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }) }), eventType.images && eventType.images.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsxRuntime.jsx("div", { style: {
|
|
14562
|
+
}, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [hasImage && (jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: availabilityBadge })), hasImage && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsxRuntime.jsx("div", { style: {
|
|
14305
14563
|
fontSize: "13px",
|
|
14306
14564
|
color: "var(--bw-surface-color)",
|
|
14307
14565
|
fontWeight: 600,
|
|
@@ -14315,7 +14573,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
|
|
|
14315
14573
|
flexDirection: "column",
|
|
14316
14574
|
justifyContent: "space-between",
|
|
14317
14575
|
height: "540px",
|
|
14318
|
-
}, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("h2", { className: "event-type-title", style: {
|
|
14576
|
+
}, children: [jsxRuntime.jsxs("div", { children: [!hasImage && (jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "flex-end", margin: "0 0 8px 0" }, children: availabilityBadge })), jsxRuntime.jsx("h2", { className: "event-type-title", style: {
|
|
14319
14577
|
fontSize: "clamp(1.1rem, 2.5vw, 24px)",
|
|
14320
14578
|
fontWeight: 700,
|
|
14321
14579
|
color: "var(--bw-text-color)",
|
|
@@ -14587,16 +14845,16 @@ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName
|
|
|
14587
14845
|
}
|
|
14588
14846
|
|
|
14589
14847
|
const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
|
|
14590
|
-
|
|
14591
|
-
if (availabilityRatio >= 0.6)
|
|
14592
|
-
return null;
|
|
14593
|
-
if (availabilityRatio === 0) {
|
|
14848
|
+
if (maxParticipants <= 0 || availableSpots <= 0) {
|
|
14594
14849
|
return {
|
|
14595
14850
|
text: t("events.soldOut"),
|
|
14596
14851
|
backgroundColor: "#7f1d1d",
|
|
14597
14852
|
textColor: "#fca5a5",
|
|
14598
14853
|
};
|
|
14599
14854
|
}
|
|
14855
|
+
const availabilityRatio = availableSpots / maxParticipants;
|
|
14856
|
+
if (availabilityRatio >= 0.6)
|
|
14857
|
+
return null;
|
|
14600
14858
|
if (availabilityRatio <= 0.3) {
|
|
14601
14859
|
return {
|
|
14602
14860
|
text: t("instances.almostSoldOut"),
|
|
@@ -14710,7 +14968,7 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
|
|
|
14710
14968
|
boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
|
|
14711
14969
|
}, children: formatCurrency(price) }));
|
|
14712
14970
|
};
|
|
14713
|
-
function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
|
|
14971
|
+
function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, waitlistEnabled = false, }) {
|
|
14714
14972
|
const t = useTranslations();
|
|
14715
14973
|
const { locale } = useLocale();
|
|
14716
14974
|
const timezone = useTimezone();
|
|
@@ -14895,12 +15153,13 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
|
|
|
14895
15153
|
? "0 2px 4px rgba(0, 0, 0, 0.2)"
|
|
14896
15154
|
: undefined,
|
|
14897
15155
|
}, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
|
|
14898
|
-
const availableSpots = event.
|
|
14899
|
-
const isFullyBooked = availableSpots === 0;
|
|
15156
|
+
const availableSpots = event.availableSpots;
|
|
14900
15157
|
const startDate = new Date(event.startTime);
|
|
14901
15158
|
const isPastEvent = today.toISOString() >= startDate.toISOString();
|
|
14902
|
-
const
|
|
14903
|
-
const
|
|
15159
|
+
const isUnavailable = availableSpots <= 0 || !event.bookingOpen;
|
|
15160
|
+
const isDisabled = isUnavailable || isPastEvent;
|
|
15161
|
+
const canWaitlist = waitlistEnabled &&
|
|
15162
|
+
isUnavailable &&
|
|
14904
15163
|
!isPastEvent &&
|
|
14905
15164
|
!!apiBaseUrl &&
|
|
14906
15165
|
!!organizationId;
|
|
@@ -14937,7 +15196,7 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
|
|
|
14937
15196
|
color: "var(--bw-highlight-color)",
|
|
14938
15197
|
opacity: 0.8,
|
|
14939
15198
|
fontSize: "32px",
|
|
14940
|
-
}, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxRuntime.jsxs("div", { style: {
|
|
15199
|
+
}, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.effectiveMaxParticipants ?? event.maxParticipants, t: t }), jsxRuntime.jsxs("div", { style: {
|
|
14941
15200
|
display: "flex",
|
|
14942
15201
|
justifyContent: "space-between",
|
|
14943
15202
|
width: "100%",
|
|
@@ -15114,18 +15373,19 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
|
|
|
15114
15373
|
margin: "0 auto",
|
|
15115
15374
|
padding: "12px 16px",
|
|
15116
15375
|
fontFamily: "var(--bw-font-family)",
|
|
15117
|
-
}, children: [jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events
|
|
15118
|
-
|
|
15119
|
-
const isFullyBooked = availableSpots === 0;
|
|
15376
|
+
}, children: [jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events
|
|
15377
|
+
.filter((event) => {
|
|
15120
15378
|
const startDate = new Date(event.startTime);
|
|
15121
15379
|
const isPastEvent = today.toISOString() >= startDate.toISOString();
|
|
15122
|
-
|
|
15123
|
-
|
|
15380
|
+
return !isPastEvent && event.availableSpots > 0 && event.bookingOpen;
|
|
15381
|
+
})
|
|
15382
|
+
.map((event) => {
|
|
15383
|
+
const availableSpots = event.availableSpots;
|
|
15384
|
+
const startDate = new Date(event.startTime);
|
|
15124
15385
|
const allocationBadge = (() => {
|
|
15386
|
+
const availabilityRatio = availableSpots / event.maxParticipants;
|
|
15125
15387
|
if (availabilityRatio >= 0.6)
|
|
15126
15388
|
return null;
|
|
15127
|
-
if (availabilityRatio === 0)
|
|
15128
|
-
return { text: t("events.soldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
|
|
15129
15389
|
if (availabilityRatio <= 0.3)
|
|
15130
15390
|
return { text: t("instances.almostSoldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
|
|
15131
15391
|
return { text: t("instances.popularDate"), backgroundColor: "#b45309", textColor: "#fbbf24" };
|
|
@@ -15139,13 +15399,9 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
|
|
|
15139
15399
|
padding: "16px 10px",
|
|
15140
15400
|
transition: "all 0.2s ease",
|
|
15141
15401
|
fontFamily: "var(--bw-font-family)",
|
|
15142
|
-
|
|
15143
|
-
filter: isDisabled ? "grayscale(40%)" : "none",
|
|
15144
|
-
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
15402
|
+
cursor: "pointer",
|
|
15145
15403
|
}, onClick: () => {
|
|
15146
|
-
|
|
15147
|
-
handleEventSelect(event.id);
|
|
15148
|
-
}
|
|
15404
|
+
handleEventSelect(event.id);
|
|
15149
15405
|
}, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
|
|
15150
15406
|
position: "absolute",
|
|
15151
15407
|
inset: 0,
|
|
@@ -15400,7 +15656,7 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
|
|
|
15400
15656
|
alignItems: "center",
|
|
15401
15657
|
backgroundColor: "var(--bw-background-color)",
|
|
15402
15658
|
}, children: [jsxRuntime.jsx("span", { style: { fontSize: "12px", color: "var(--bw-text-muted)" }, children: isFullyBooked
|
|
15403
|
-
? t("
|
|
15659
|
+
? t("events.soldOut")
|
|
15404
15660
|
: t("specials.spotsLeft").replace("{{count}}", String(special.availableSpots)) }), jsxRuntime.jsxs("span", { style: {
|
|
15405
15661
|
fontSize: "13px",
|
|
15406
15662
|
fontWeight: 600,
|
|
@@ -15409,6 +15665,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
|
|
|
15409
15665
|
}) })] }));
|
|
15410
15666
|
}
|
|
15411
15667
|
|
|
15668
|
+
function toMinutesForUnit(unit) {
|
|
15669
|
+
switch (unit.toLowerCase()) {
|
|
15670
|
+
case "minute":
|
|
15671
|
+
case "minutes":
|
|
15672
|
+
return 1;
|
|
15673
|
+
case "hour":
|
|
15674
|
+
case "hours":
|
|
15675
|
+
return 60;
|
|
15676
|
+
case "day":
|
|
15677
|
+
case "days":
|
|
15678
|
+
return 24 * 60;
|
|
15679
|
+
default:
|
|
15680
|
+
return 1;
|
|
15681
|
+
}
|
|
15682
|
+
}
|
|
15683
|
+
// --- Timezone helpers ------------------------------------------------------
|
|
15684
|
+
// The booking is evaluated server-side against the organization's local
|
|
15685
|
+
// operating hours. The customer's browser may be in a different timezone, so we
|
|
15686
|
+
// interpret the datetime-local field as a wall-clock time in the ORG timezone
|
|
15687
|
+
// (not the browser's) and convert to a UTC instant for the API.
|
|
15688
|
+
function getTzParts(date, timeZone) {
|
|
15689
|
+
const dtf = new Intl.DateTimeFormat("en-CA", {
|
|
15690
|
+
timeZone,
|
|
15691
|
+
hour12: false,
|
|
15692
|
+
year: "numeric",
|
|
15693
|
+
month: "2-digit",
|
|
15694
|
+
day: "2-digit",
|
|
15695
|
+
hour: "2-digit",
|
|
15696
|
+
minute: "2-digit",
|
|
15697
|
+
second: "2-digit",
|
|
15698
|
+
});
|
|
15699
|
+
const parts = {};
|
|
15700
|
+
for (const part of dtf.formatToParts(date)) {
|
|
15701
|
+
if (part.type !== "literal")
|
|
15702
|
+
parts[part.type] = part.value;
|
|
15703
|
+
}
|
|
15704
|
+
return parts;
|
|
15705
|
+
}
|
|
15706
|
+
function tzOffsetMs(utcMs, timeZone) {
|
|
15707
|
+
const p = getTzParts(new Date(utcMs), timeZone);
|
|
15708
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15709
|
+
const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
|
|
15710
|
+
return asLocalUtc - utcMs;
|
|
15711
|
+
}
|
|
15712
|
+
/** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
|
|
15713
|
+
function zonedWallTimeToUtc(wallTime, timeZone) {
|
|
15714
|
+
const [datePart, timePart] = wallTime.split("T");
|
|
15715
|
+
if (!datePart || !timePart)
|
|
15716
|
+
return new Date(NaN);
|
|
15717
|
+
const [y, mo, d] = datePart.split("-").map(Number);
|
|
15718
|
+
const [h, mi] = timePart.split(":").map(Number);
|
|
15719
|
+
const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
|
|
15720
|
+
// Single-pass offset correction (good enough outside the ~1h DST fold).
|
|
15721
|
+
const offset = tzOffsetMs(naiveUtc, timeZone);
|
|
15722
|
+
return new Date(naiveUtc - offset);
|
|
15723
|
+
}
|
|
15724
|
+
/** Render a UTC instant as an org-tz datetime-local input value. */
|
|
15725
|
+
function utcToZonedInputValue(date, timeZone) {
|
|
15726
|
+
const p = getTzParts(date, timeZone);
|
|
15727
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15728
|
+
return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
|
|
15729
|
+
}
|
|
15730
|
+
/** Format a UTC instant for display in the org timezone. */
|
|
15731
|
+
function formatInTimeZone(date, timeZone, locale) {
|
|
15732
|
+
return new Intl.DateTimeFormat(locale, {
|
|
15733
|
+
timeZone,
|
|
15734
|
+
dateStyle: "medium",
|
|
15735
|
+
timeStyle: "short",
|
|
15736
|
+
}).format(date);
|
|
15737
|
+
}
|
|
15738
|
+
function calculateTieredPrice(durationUnits, config) {
|
|
15739
|
+
const tiers = [...(config.tiers ?? [])]
|
|
15740
|
+
.filter((tier) => tier.unitCount > 0 && tier.price >= 0)
|
|
15741
|
+
.sort((a, b) => b.unitCount - a.unitCount);
|
|
15742
|
+
if (tiers.length === 0) {
|
|
15743
|
+
return 0;
|
|
15744
|
+
}
|
|
15745
|
+
let total = 0;
|
|
15746
|
+
let remaining = durationUnits;
|
|
15747
|
+
for (const tier of tiers) {
|
|
15748
|
+
const count = Math.floor(remaining / tier.unitCount);
|
|
15749
|
+
if (count <= 0)
|
|
15750
|
+
continue;
|
|
15751
|
+
total += count * tier.price;
|
|
15752
|
+
remaining -= count * tier.unitCount;
|
|
15753
|
+
}
|
|
15754
|
+
if (remaining > 0) {
|
|
15755
|
+
if (typeof config.perUnitPrice === "number") {
|
|
15756
|
+
total += remaining * config.perUnitPrice;
|
|
15757
|
+
}
|
|
15758
|
+
else {
|
|
15759
|
+
total += tiers[tiers.length - 1]?.price ?? 0;
|
|
15760
|
+
}
|
|
15761
|
+
}
|
|
15762
|
+
return total;
|
|
15763
|
+
}
|
|
15764
|
+
function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
|
|
15765
|
+
const t = useTranslations();
|
|
15766
|
+
const { locale } = useLocale();
|
|
15767
|
+
const freeformConfig = eventType.freeformConfig;
|
|
15768
|
+
const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
15769
|
+
const isNamedResource = eventType.resourceKind === "named";
|
|
15770
|
+
const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
|
|
15771
|
+
const now = React.useMemo(() => {
|
|
15772
|
+
const next = new Date();
|
|
15773
|
+
next.setMinutes(next.getMinutes() + 30, 0, 0);
|
|
15774
|
+
return next;
|
|
15775
|
+
}, []);
|
|
15776
|
+
const [startInput, setStartInput] = React.useState(utcToZonedInputValue(now, timeZone));
|
|
15777
|
+
const [duration, setDuration] = React.useState(Math.max(1, freeformConfig?.minDuration ?? 1));
|
|
15778
|
+
const [requestedUnits, setRequestedUnits] = React.useState(1);
|
|
15779
|
+
const [customerName, setCustomerName] = React.useState("");
|
|
15780
|
+
const [customerEmail, setCustomerEmail] = React.useState("");
|
|
15781
|
+
const [customerPhone, setCustomerPhone] = React.useState("");
|
|
15782
|
+
const [comment, setComment] = React.useState("");
|
|
15783
|
+
const [acceptTerms, setAcceptTerms] = React.useState(false);
|
|
15784
|
+
const [checkoutStep, setCheckoutStep] = React.useState("details");
|
|
15785
|
+
const [availability, setAvailability] = React.useState(null);
|
|
15786
|
+
const [availabilityError, setAvailabilityError] = React.useState(null);
|
|
15787
|
+
const [isCheckingAvailability, setIsCheckingAvailability] = React.useState(false);
|
|
15788
|
+
React.useEffect(() => {
|
|
15789
|
+
if (!isOpen)
|
|
15790
|
+
return;
|
|
15791
|
+
setCheckoutStep("details");
|
|
15792
|
+
}, [isOpen]);
|
|
15793
|
+
React.useEffect(() => {
|
|
15794
|
+
if (!freeformConfig)
|
|
15795
|
+
return;
|
|
15796
|
+
setDuration(Math.max(1, freeformConfig.minDuration));
|
|
15797
|
+
setRequestedUnits(1);
|
|
15798
|
+
setAvailability(null);
|
|
15799
|
+
setAvailabilityError(null);
|
|
15800
|
+
}, [freeformConfig?.resourceId]);
|
|
15801
|
+
const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
|
|
15802
|
+
const startDate = React.useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
|
|
15803
|
+
const endDate = React.useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
|
|
15804
|
+
React.useEffect(() => {
|
|
15805
|
+
if (!isOpen || !freeformConfig)
|
|
15806
|
+
return;
|
|
15807
|
+
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
|
|
15808
|
+
return;
|
|
15809
|
+
const timer = setTimeout(async () => {
|
|
15810
|
+
setIsCheckingAvailability(true);
|
|
15811
|
+
setAvailabilityError(null);
|
|
15812
|
+
try {
|
|
15813
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
|
|
15814
|
+
method: "POST",
|
|
15815
|
+
headers: createApiHeaders(config, locale),
|
|
15816
|
+
body: JSON.stringify(createRequestBody(config, {
|
|
15817
|
+
eventTypeId: eventType.id,
|
|
15818
|
+
requestedUnits,
|
|
15819
|
+
start: startDate.toISOString(),
|
|
15820
|
+
end: endDate.toISOString(),
|
|
15821
|
+
})),
|
|
15822
|
+
});
|
|
15823
|
+
const data = await response.json();
|
|
15824
|
+
if (!response.ok) {
|
|
15825
|
+
setAvailability(null);
|
|
15826
|
+
setAvailabilityError(data.error || t("error.loadBookingData"));
|
|
15827
|
+
return;
|
|
15828
|
+
}
|
|
15829
|
+
setAvailability({
|
|
15830
|
+
available: Boolean(data.available),
|
|
15831
|
+
freeUnits: Number(data.freeUnits ?? 0),
|
|
15832
|
+
capacityUnits: Number(data.capacityUnits ?? 0),
|
|
15833
|
+
usedUnits: Number(data.usedUnits ?? 0),
|
|
15834
|
+
reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
|
|
15835
|
+
blockUsedUnits: Number(data.blockUsedUnits ?? 0),
|
|
15836
|
+
durationUnits: Number(data.durationUnits ?? 0),
|
|
15837
|
+
roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
|
|
15838
|
+
});
|
|
15839
|
+
}
|
|
15840
|
+
catch (_error) {
|
|
15841
|
+
setAvailability(null);
|
|
15842
|
+
setAvailabilityError(t("error.loadBookingData"));
|
|
15843
|
+
}
|
|
15844
|
+
finally {
|
|
15845
|
+
setIsCheckingAvailability(false);
|
|
15846
|
+
}
|
|
15847
|
+
}, 350);
|
|
15848
|
+
return () => clearTimeout(timer);
|
|
15849
|
+
}, [
|
|
15850
|
+
config,
|
|
15851
|
+
endDate,
|
|
15852
|
+
eventType.id,
|
|
15853
|
+
freeformConfig,
|
|
15854
|
+
isOpen,
|
|
15855
|
+
locale,
|
|
15856
|
+
requestedUnits,
|
|
15857
|
+
startDate,
|
|
15858
|
+
t,
|
|
15859
|
+
]);
|
|
15860
|
+
const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
|
|
15861
|
+
const basePrice = React.useMemo(() => {
|
|
15862
|
+
if (!freeformConfig)
|
|
15863
|
+
return 0;
|
|
15864
|
+
if (freeformConfig.pricingModel === "perUnit") {
|
|
15865
|
+
return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
|
|
15866
|
+
}
|
|
15867
|
+
return calculateTieredPrice(billedDurationUnits, freeformConfig);
|
|
15868
|
+
}, [billedDurationUnits, freeformConfig]);
|
|
15869
|
+
const totalPrice = Math.max(0, basePrice * requestedUnits);
|
|
15870
|
+
const canContinueToPayment = !!freeformConfig &&
|
|
15871
|
+
customerName.trim().length >= 2 &&
|
|
15872
|
+
customerEmail.trim().length > 3 &&
|
|
15873
|
+
acceptTerms &&
|
|
15874
|
+
!!availability?.available &&
|
|
15875
|
+
!isCheckingAvailability;
|
|
15876
|
+
const freeformSelection = React.useMemo(() => ({
|
|
15877
|
+
eventTypeId: eventType.id,
|
|
15878
|
+
start: startDate.toISOString(),
|
|
15879
|
+
end: endDate.toISOString(),
|
|
15880
|
+
requestedUnits,
|
|
15881
|
+
customerName: customerName.trim(),
|
|
15882
|
+
customerEmail: customerEmail.trim(),
|
|
15883
|
+
customerPhone: customerPhone.trim(),
|
|
15884
|
+
comment: comment.trim(),
|
|
15885
|
+
}), [
|
|
15886
|
+
comment,
|
|
15887
|
+
customerEmail,
|
|
15888
|
+
customerName,
|
|
15889
|
+
customerPhone,
|
|
15890
|
+
endDate,
|
|
15891
|
+
eventType.id,
|
|
15892
|
+
requestedUnits,
|
|
15893
|
+
startDate,
|
|
15894
|
+
]);
|
|
15895
|
+
const paymentFormData = React.useMemo(() => ({
|
|
15896
|
+
customerName: customerName.trim(),
|
|
15897
|
+
customerEmail: customerEmail.trim(),
|
|
15898
|
+
customerPhone: customerPhone.trim(),
|
|
15899
|
+
participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
|
|
15900
|
+
comment: comment.trim(),
|
|
15901
|
+
}), [comment, customerEmail, customerName, customerPhone]);
|
|
15902
|
+
const eventDetailsForPayment = React.useMemo(() => ({
|
|
15903
|
+
id: eventType.id,
|
|
15904
|
+
name: eventType.name,
|
|
15905
|
+
startTime: startDate.toISOString(),
|
|
15906
|
+
endTime: endDate.toISOString(),
|
|
15907
|
+
price: basePrice,
|
|
15908
|
+
maxParticipants: availability?.capacityUnits ?? requestedUnits,
|
|
15909
|
+
participantCount: 0,
|
|
15910
|
+
availableSpots: availability?.freeUnits ?? requestedUnits,
|
|
15911
|
+
durationDays: 1,
|
|
15912
|
+
durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
|
|
15913
|
+
images: eventType.images ?? [],
|
|
15914
|
+
category: eventType.category,
|
|
15915
|
+
organization: {
|
|
15916
|
+
id: config.organizationId,
|
|
15917
|
+
name: "",
|
|
15918
|
+
},
|
|
15919
|
+
bookingOpen: availability?.available ?? false,
|
|
15920
|
+
}), [
|
|
15921
|
+
availability?.available,
|
|
15922
|
+
availability?.capacityUnits,
|
|
15923
|
+
availability?.freeUnits,
|
|
15924
|
+
availability?.roundedDurationMinutes,
|
|
15925
|
+
basePrice,
|
|
15926
|
+
config.organizationId,
|
|
15927
|
+
duration,
|
|
15928
|
+
endDate,
|
|
15929
|
+
eventType.category,
|
|
15930
|
+
eventType.id,
|
|
15931
|
+
eventType.images,
|
|
15932
|
+
eventType.name,
|
|
15933
|
+
requestedUnits,
|
|
15934
|
+
startDate,
|
|
15935
|
+
unitMinutes,
|
|
15936
|
+
]);
|
|
15937
|
+
const footer = (jsxRuntime.jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsxRuntime.jsx("button", { type: "button", onClick: () => {
|
|
15938
|
+
if (checkoutStep === "payment") {
|
|
15939
|
+
setCheckoutStep("details");
|
|
15940
|
+
}
|
|
15941
|
+
else {
|
|
15942
|
+
onClose();
|
|
15943
|
+
}
|
|
15944
|
+
}, style: {
|
|
15945
|
+
flex: 1,
|
|
15946
|
+
padding: "12px 16px",
|
|
15947
|
+
border: "1px solid var(--bw-border-color)",
|
|
15948
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15949
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
15950
|
+
color: "var(--bw-text-color)",
|
|
15951
|
+
fontFamily: "var(--bw-font-family)",
|
|
15952
|
+
fontWeight: 600,
|
|
15953
|
+
cursor: "pointer",
|
|
15954
|
+
}, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsxRuntime.jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
|
|
15955
|
+
flex: 1,
|
|
15956
|
+
padding: "12px 16px",
|
|
15957
|
+
border: "none",
|
|
15958
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15959
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
15960
|
+
color: "var(--bw-button-text-color, #ffffff)",
|
|
15961
|
+
fontFamily: "var(--bw-font-family)",
|
|
15962
|
+
fontWeight: 700,
|
|
15963
|
+
cursor: canContinueToPayment ? "pointer" : "not-allowed",
|
|
15964
|
+
opacity: canContinueToPayment ? 1 : 0.6,
|
|
15965
|
+
}, children: t("button.continueToPayment") }))] }));
|
|
15966
|
+
if (!freeformConfig) {
|
|
15967
|
+
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: jsxRuntime.jsx("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
|
|
15968
|
+
}
|
|
15969
|
+
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: jsxRuntime.jsx("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), jsxRuntime.jsx("input", { type: "datetime-local", value: startInput, onChange: (event) => setStartInput(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), jsxRuntime.jsx("input", { type: "number", min: freeformConfig.minDuration, max: freeformConfig.maxDuration, step: freeformConfig.stepDuration, value: duration, onChange: (event) => setDuration(Number(event.target.value)), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), !isNamedResource && (jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), jsxRuntime.jsx("input", { type: "number", min: 1, max: maxUnits, value: requestedUnits, onChange: (event) => setRequestedUnits(Math.min(maxUnits, Math.max(1, Number(event.target.value)))), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }))] }), jsxRuntime.jsxs("div", { style: { marginTop: "12px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: [formatInTimeZone(startDate, timeZone, locale), " - ", formatInTimeZone(endDate, timeZone, locale), eventType.organizationTimezone ? ` (${timeZone})` : ""] }), isCheckingAvailability && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: availability.available ? "var(--bw-success-color)" : "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availability.available
|
|
15970
|
+
? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
|
|
15971
|
+
: t("booking.freeformNotAvailable") }))] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsx("input", { type: "text", placeholder: t("booking.namePlaceholder"), value: customerName, onChange: (event) => setCustomerName(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("input", { type: "email", placeholder: t("booking.emailPlaceholder"), value: customerEmail, onChange: (event) => setCustomerEmail(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("input", { type: "tel", placeholder: t("booking.phonePlaceholder"), value: customerPhone, onChange: (event) => setCustomerPhone(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("textarea", { placeholder: t("booking.commentPlaceholder"), value: comment, onChange: (event) => setComment(event.target.value), rows: 3, style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)", resize: "vertical" } }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { children: t("booking.duration") }), jsxRuntime.jsx("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [jsxRuntime.jsx("span", { children: t("booking.price") }), jsxRuntime.jsxs("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-color)", fontFamily: "var(--bw-font-family)", fontSize: "20px", fontWeight: 700, marginTop: "10px" }, children: [jsxRuntime.jsx("span", { children: t("summary.totalAmount") }), jsxRuntime.jsx("span", { children: formatCurrency(totalPrice) })] })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(HoldCountdown, {}), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (jsxRuntime.jsx("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode, freeformSelection: freeformSelection })), systemConfig?.paymentProvider === "stripe" && (jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
|
|
15972
|
+
}
|
|
15973
|
+
|
|
15412
15974
|
const getThemeConfig = (theme = "generic") => {
|
|
15413
15975
|
switch (theme) {
|
|
15414
15976
|
case "christmas":
|
|
@@ -16043,12 +16605,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16043
16605
|
const [isSuccess, setIsSuccess] = React.useState(false);
|
|
16044
16606
|
const [successPaymentId, setSuccessPaymentId] = React.useState(null);
|
|
16045
16607
|
const [systemConfig, setSystemConfig] = React.useState(null);
|
|
16608
|
+
const [waitlistEnabled, setWaitlistEnabled] = React.useState(false);
|
|
16046
16609
|
// When true, loadEventInstances skips the single-instance auto-select (used by handleShowAllEvents)
|
|
16047
16610
|
const skipInstanceAutoSelectRef = React.useRef(false);
|
|
16048
16611
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
16049
16612
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
|
|
16050
16613
|
const [shouldRenderUpsells, setShouldRenderUpsells] = React.useState(false);
|
|
16051
16614
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = React.useState(false);
|
|
16615
|
+
const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = React.useState(false);
|
|
16052
16616
|
// Google Ads config (received from API, set once from the first API response)
|
|
16053
16617
|
const [googleAdsConfig, setGoogleAdsConfig] = React.useState(null);
|
|
16054
16618
|
const extractGoogleAdsConfig = (data) => {
|
|
@@ -16062,6 +16626,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16062
16626
|
// Voucher purchase state
|
|
16063
16627
|
const [voucherConfig, setVoucherConfig] = React.useState(null);
|
|
16064
16628
|
const [voucherEventTypes, setVoucherEventTypes] = React.useState([]);
|
|
16629
|
+
const [voucherCategories, setVoucherCategories] = React.useState([]);
|
|
16065
16630
|
const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = React.useState(false);
|
|
16066
16631
|
const [isVoucherFormOpen, setIsVoucherFormOpen] = React.useState(false);
|
|
16067
16632
|
const [voucherPurchaseResult, setVoucherPurchaseResult] = React.useState(null);
|
|
@@ -16141,6 +16706,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16141
16706
|
setVoucherConfig(mergedConfig);
|
|
16142
16707
|
extractGoogleAdsConfig(data);
|
|
16143
16708
|
setVoucherEventTypes(data.eventTypes || []);
|
|
16709
|
+
setVoucherCategories(data.categories || []);
|
|
16144
16710
|
// Set system config for payment processing
|
|
16145
16711
|
if (data.paymentProvider) {
|
|
16146
16712
|
setSystemConfig({
|
|
@@ -16169,7 +16735,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16169
16735
|
if (!analyticsInitRef.current && config.organizationId) {
|
|
16170
16736
|
analyticsInitRef.current = true;
|
|
16171
16737
|
initAnalytics(config.apiBaseUrl, config.organizationId, {
|
|
16172
|
-
partnerContractId: config.partnerContractId,
|
|
16738
|
+
...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
|
|
16173
16739
|
});
|
|
16174
16740
|
trackEvent("widget_loaded", {
|
|
16175
16741
|
viewMode,
|
|
@@ -16533,6 +17099,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16533
17099
|
}
|
|
16534
17100
|
extractGoogleAdsConfig(data);
|
|
16535
17101
|
setEventInstances(data.eventInstances);
|
|
17102
|
+
setWaitlistEnabled(data.waitlistEnabled ?? false);
|
|
16536
17103
|
if (data.paymentProvider) {
|
|
16537
17104
|
setSystemConfig({
|
|
16538
17105
|
paymentProvider: data.paymentProvider,
|
|
@@ -16648,6 +17215,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16648
17215
|
const handleEventTypeSelect = async (eventType) => {
|
|
16649
17216
|
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16650
17217
|
setSelectedEventType(eventType);
|
|
17218
|
+
if (eventType.bookingMode === "freeform") {
|
|
17219
|
+
setCurrentStep("freeform");
|
|
17220
|
+
setShouldRenderFreeformSelection(true);
|
|
17221
|
+
setIsLoadingEventInstances(true);
|
|
17222
|
+
try {
|
|
17223
|
+
await loadEventInstances(eventType.id);
|
|
17224
|
+
}
|
|
17225
|
+
finally {
|
|
17226
|
+
setIsLoadingEventInstances(false);
|
|
17227
|
+
}
|
|
17228
|
+
return;
|
|
17229
|
+
}
|
|
16651
17230
|
setCurrentStep("eventInstances");
|
|
16652
17231
|
setShouldRenderInstanceSelection(true);
|
|
16653
17232
|
setIsLoadingEventInstances(true);
|
|
@@ -16738,6 +17317,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16738
17317
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16739
17318
|
setSidebarOpen(false);
|
|
16740
17319
|
setShouldRenderBookingForm(false);
|
|
17320
|
+
setShouldRenderFreeformSelection(false);
|
|
16741
17321
|
setBookingPersistedState(null);
|
|
16742
17322
|
config.onSuccess?.(result);
|
|
16743
17323
|
};
|
|
@@ -17053,6 +17633,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17053
17633
|
setShouldRenderInstanceSelection(false);
|
|
17054
17634
|
setShouldRenderUpsells(false);
|
|
17055
17635
|
setShouldRenderBookingForm(false);
|
|
17636
|
+
setShouldRenderFreeformSelection(false);
|
|
17056
17637
|
setSelectedUpsells([]);
|
|
17057
17638
|
setUpsells([]);
|
|
17058
17639
|
setBookingPersistedState(null);
|
|
@@ -17074,6 +17655,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17074
17655
|
setShouldRenderInstanceSelection(false);
|
|
17075
17656
|
setShouldRenderUpsells(false);
|
|
17076
17657
|
setShouldRenderBookingForm(false);
|
|
17658
|
+
setShouldRenderFreeformSelection(false);
|
|
17077
17659
|
setSelectedUpsells([]);
|
|
17078
17660
|
setUpsells([]);
|
|
17079
17661
|
setBookingPersistedState(null);
|
|
@@ -17086,13 +17668,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17086
17668
|
}, isOpen: currentStep === "eventInstances", onClose: () => {
|
|
17087
17669
|
setShowingPreview(true);
|
|
17088
17670
|
setCurrentStep("eventTypes");
|
|
17089
|
-
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17671
|
+
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17090
17672
|
setIsSuccess(false);
|
|
17091
17673
|
setCurrentStep("eventTypes");
|
|
17092
17674
|
setShowingPreview(true);
|
|
17093
17675
|
setSuccessPaymentId(null);
|
|
17094
17676
|
setShouldRenderInstanceSelection(false);
|
|
17095
17677
|
setShouldRenderBookingForm(false);
|
|
17678
|
+
setShouldRenderFreeformSelection(false);
|
|
17096
17679
|
const url = new URL(window.location.href);
|
|
17097
17680
|
url.searchParams.delete("payment_intent");
|
|
17098
17681
|
url.searchParams.delete("payment_intent_client_secret");
|
|
@@ -17136,13 +17719,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17136
17719
|
setCurrentStep("booking");
|
|
17137
17720
|
setShouldRenderBookingForm(true);
|
|
17138
17721
|
}
|
|
17722
|
+
else if (selectedEventType?.bookingMode === "freeform") {
|
|
17723
|
+
setCurrentStep("freeform");
|
|
17724
|
+
setShouldRenderFreeformSelection(true);
|
|
17725
|
+
}
|
|
17139
17726
|
else {
|
|
17140
17727
|
setCurrentStep("eventInstances");
|
|
17141
17728
|
setSidebarOpen(true);
|
|
17142
17729
|
setShouldRenderInstanceSelection(true);
|
|
17143
17730
|
}
|
|
17144
17731
|
}, children: [isButtonBusy && jsxRuntime.jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
|
|
17145
|
-
(isDirectInstanceMode
|
|
17732
|
+
(isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
|
|
17733
|
+
? t("button.bookNow")
|
|
17734
|
+
: t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
|
|
17735
|
+
setCurrentStep("eventTypes");
|
|
17736
|
+
setSidebarOpen(false);
|
|
17737
|
+
setShouldRenderFreeformSelection(false);
|
|
17738
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17146
17739
|
setIsSuccess(false);
|
|
17147
17740
|
setCurrentStep("eventTypes");
|
|
17148
17741
|
setSidebarOpen(false);
|
|
@@ -17150,6 +17743,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17150
17743
|
setShouldRenderInstanceSelection(false);
|
|
17151
17744
|
setShouldRenderUpsells(false);
|
|
17152
17745
|
setShouldRenderBookingForm(false);
|
|
17746
|
+
setShouldRenderFreeformSelection(false);
|
|
17153
17747
|
setSelectedUpsells([]);
|
|
17154
17748
|
setUpsells([]);
|
|
17155
17749
|
const url = new URL(window.location.href);
|
|
@@ -17162,7 +17756,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17162
17756
|
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
17163
17757
|
}
|
|
17164
17758
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
17165
|
-
const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
|
|
17759
|
+
const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick, showImages: config.showEventImages !== false })), isStandaloneVoucherMode && (jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
|
|
17166
17760
|
display: "inline-block",
|
|
17167
17761
|
width: "32px",
|
|
17168
17762
|
height: "32px",
|
|
@@ -17203,13 +17797,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17203
17797
|
};
|
|
17204
17798
|
};
|
|
17205
17799
|
const backHandlers = getBackHandlers();
|
|
17206
|
-
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsxRuntime.jsx(
|
|
17800
|
+
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
|
|
17801
|
+
setCurrentStep("eventTypes");
|
|
17802
|
+
setShouldRenderFreeformSelection(false);
|
|
17803
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
|
|
17207
17804
|
setIsSuccess(false);
|
|
17208
17805
|
setCurrentStep("eventTypes");
|
|
17209
17806
|
setSuccessPaymentId(null);
|
|
17210
17807
|
setShouldRenderInstanceSelection(false);
|
|
17211
17808
|
setShouldRenderUpsells(false);
|
|
17212
17809
|
setShouldRenderBookingForm(false);
|
|
17810
|
+
setShouldRenderFreeformSelection(false);
|
|
17213
17811
|
setSelectedUpsells([]);
|
|
17214
17812
|
setUpsells([]);
|
|
17215
17813
|
const url = new URL(window.location.href);
|
|
@@ -17219,7 +17817,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17219
17817
|
url.searchParams.delete("mollie_payment_id");
|
|
17220
17818
|
url.searchParams.delete("mollie_status");
|
|
17221
17819
|
window.history.replaceState({}, "", url.toString());
|
|
17222
|
-
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
|
|
17820
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
|
|
17223
17821
|
setIsSuccess(false);
|
|
17224
17822
|
setVoucherPurchaseResult(null);
|
|
17225
17823
|
const url = new URL(window.location.href);
|
|
@@ -17267,7 +17865,7 @@ function styleInject(css, ref) {
|
|
|
17267
17865
|
}
|
|
17268
17866
|
}
|
|
17269
17867
|
|
|
17270
|
-
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}}";
|
|
17868
|
+
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}}";
|
|
17271
17869
|
styleInject(css_248z);
|
|
17272
17870
|
|
|
17273
17871
|
// Export init function for vanilla JS usage
|