@bigz-app/booking-widget 1.4.5 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/booking-widget.js +625 -74
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts +18 -0
- 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/EventTypeSelection.d.ts +15 -0
- 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/index.d.ts +1 -0
- package/dist/components/events/index.d.ts.map +1 -1
- package/dist/components/voucher/VoucherIntegration.d.ts +3 -2
- package/dist/components/voucher/VoucherIntegration.d.ts.map +1 -1
- package/dist/components/voucher/VoucherPurchaseForm.d.ts +8 -2
- package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
- package/dist/components/voucher/index.d.ts +1 -1
- package/dist/components/voucher/index.d.ts.map +1 -1
- package/dist/components/voucher/useVoucherConfig.d.ts +6 -0
- package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/index.cjs +625 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +625 -74
- package/dist/index.esm.js.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}}",
|
|
@@ -822,6 +844,13 @@ const es = {
|
|
|
822
844
|
"voucher.monetaryTypeDesc": "Elige cualquier cantidad",
|
|
823
845
|
"voucher.eventType": "Vale de experiencia",
|
|
824
846
|
"voucher.eventTypeDesc": "Para una experiencia específica",
|
|
847
|
+
"voucher.categoryType": "Vale de categoría",
|
|
848
|
+
"voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
|
|
849
|
+
"voucher.categoryVoucher": "Vale de categoría",
|
|
850
|
+
"voucher.selectCategory": "Seleccionar categoría",
|
|
851
|
+
"voucher.categoryLabel": "Categoría",
|
|
852
|
+
"voucher.selectCategoryPlaceholder": "Elige una categoría...",
|
|
853
|
+
"voucher.categoryAmount": "Valor del vale",
|
|
825
854
|
"voucher.selectAmount": "Selecciona el importe",
|
|
826
855
|
"voucher.customAmount": "O introduce un importe personalizado",
|
|
827
856
|
"voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
|
|
@@ -1112,6 +1141,13 @@ const pt = {
|
|
|
1112
1141
|
"voucher.monetaryTypeDesc": "Escolha qualquer quantia",
|
|
1113
1142
|
"voucher.eventType": "Vale de experiência",
|
|
1114
1143
|
"voucher.eventTypeDesc": "Para uma experiência específica",
|
|
1144
|
+
"voucher.categoryType": "Vale de categoria",
|
|
1145
|
+
"voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
|
|
1146
|
+
"voucher.categoryVoucher": "Vale de categoria",
|
|
1147
|
+
"voucher.selectCategory": "Selecionar categoria",
|
|
1148
|
+
"voucher.categoryLabel": "Categoria",
|
|
1149
|
+
"voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
|
|
1150
|
+
"voucher.categoryAmount": "Valor do vale",
|
|
1115
1151
|
"voucher.selectAmount": "Selecione o valor",
|
|
1116
1152
|
"voucher.customAmount": "Ou introduza um valor personalizado",
|
|
1117
1153
|
"voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
|
|
@@ -1402,6 +1438,13 @@ const sv = {
|
|
|
1402
1438
|
"voucher.monetaryTypeDesc": "Välj valfritt belopp",
|
|
1403
1439
|
"voucher.eventType": "Upplevelsebevis",
|
|
1404
1440
|
"voucher.eventTypeDesc": "För en specifik upplevelse",
|
|
1441
|
+
"voucher.categoryType": "Kategoripresent",
|
|
1442
|
+
"voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
|
|
1443
|
+
"voucher.categoryVoucher": "Kategoripresent",
|
|
1444
|
+
"voucher.selectCategory": "Välj kategori",
|
|
1445
|
+
"voucher.categoryLabel": "Kategori",
|
|
1446
|
+
"voucher.selectCategoryPlaceholder": "Välj en kategori...",
|
|
1447
|
+
"voucher.categoryAmount": "Presentkortsvärde",
|
|
1405
1448
|
"voucher.selectAmount": "Välj belopp",
|
|
1406
1449
|
"voucher.customAmount": "Eller ange eget belopp",
|
|
1407
1450
|
"voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
|
|
@@ -6372,7 +6415,7 @@ function loadMollieScript() {
|
|
|
6372
6415
|
document.head.appendChild(script);
|
|
6373
6416
|
});
|
|
6374
6417
|
}
|
|
6375
|
-
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
|
|
6418
|
+
function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
|
|
6376
6419
|
const t = useTranslations();
|
|
6377
6420
|
const { locale } = useLocale();
|
|
6378
6421
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
@@ -6386,7 +6429,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6386
6429
|
const cardFormRef = React.useRef(null);
|
|
6387
6430
|
const cardContainerRef = React.useRef(null);
|
|
6388
6431
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6389
|
-
const isFullyCoveredByGiftCards =
|
|
6432
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
6433
|
+
? false
|
|
6434
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
6390
6435
|
const isCreditCard = selectedMethod === "creditcard";
|
|
6391
6436
|
React.useEffect(() => {
|
|
6392
6437
|
if (isFullyCoveredByGiftCards)
|
|
@@ -6496,6 +6541,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6496
6541
|
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6497
6542
|
}
|
|
6498
6543
|
const validateBeforePayment = () => {
|
|
6544
|
+
if (freeformSelection) {
|
|
6545
|
+
return null;
|
|
6546
|
+
}
|
|
6499
6547
|
const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
|
|
6500
6548
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6501
6549
|
if (participantCount > availableSpots) {
|
|
@@ -6507,21 +6555,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6507
6555
|
return null;
|
|
6508
6556
|
};
|
|
6509
6557
|
const buildRequestData = (cardToken) => ({
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6558
|
+
...(freeformSelection
|
|
6559
|
+
? {
|
|
6560
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
6561
|
+
organizationId: config.organizationId,
|
|
6562
|
+
start: freeformSelection.start,
|
|
6563
|
+
end: freeformSelection.end,
|
|
6564
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
6565
|
+
amount: Math.round(totalAmount),
|
|
6566
|
+
currency: "EUR",
|
|
6567
|
+
customerName: freeformSelection.customerName.trim(),
|
|
6568
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
6569
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
6570
|
+
comment: freeformSelection.comment?.trim(),
|
|
6571
|
+
locale,
|
|
6572
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6573
|
+
...(cardToken && { cardToken }),
|
|
6574
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6575
|
+
}
|
|
6576
|
+
: {
|
|
6577
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
6578
|
+
organizationId: config.organizationId,
|
|
6579
|
+
amount: Math.round(totalAmount),
|
|
6580
|
+
currency: "EUR",
|
|
6581
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
6582
|
+
discountCode: discountCode?.code,
|
|
6583
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
6584
|
+
customerName: formData.customerName?.trim(),
|
|
6585
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
6586
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
6587
|
+
comment: formData.comment?.trim(),
|
|
6588
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
6589
|
+
...(cardToken && { cardToken }),
|
|
6590
|
+
...(selectedMethod && !cardToken && { method: selectedMethod }),
|
|
6591
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
6592
|
+
}),
|
|
6525
6593
|
});
|
|
6526
6594
|
const handleCardPayment = async () => {
|
|
6527
6595
|
if (!mollieRef.current)
|
|
@@ -6539,7 +6607,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6539
6607
|
setPaymentError(tokenError?.message || t("error.createPayment"));
|
|
6540
6608
|
return;
|
|
6541
6609
|
}
|
|
6542
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6610
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6611
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6612
|
+
: "/booking/create-mollie-payment"), {
|
|
6543
6613
|
method: "POST",
|
|
6544
6614
|
headers: createApiHeaders(config, locale),
|
|
6545
6615
|
body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
|
|
@@ -6575,7 +6645,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6575
6645
|
setPaymentError(validationError);
|
|
6576
6646
|
return;
|
|
6577
6647
|
}
|
|
6578
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
6648
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
|
|
6649
|
+
? "/booking/create-freeform-mollie-payment"
|
|
6650
|
+
: "/booking/create-mollie-payment"), {
|
|
6579
6651
|
method: "POST",
|
|
6580
6652
|
headers: createApiHeaders(config, locale),
|
|
6581
6653
|
body: JSON.stringify(createRequestBody(config, buildRequestData())),
|
|
@@ -6603,7 +6675,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6603
6675
|
? t("button.processingPayment")
|
|
6604
6676
|
: selectedMethodData
|
|
6605
6677
|
? t("payment.payWithMethod", { method: selectedMethodData.description })
|
|
6606
|
-
: totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6678
|
+
: !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
|
|
6607
6679
|
? t("button.depositAndBook")
|
|
6608
6680
|
: t("button.bookNow");
|
|
6609
6681
|
const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
|
|
@@ -6910,12 +6982,18 @@ var loadStripe = function loadStripe() {
|
|
|
6910
6982
|
};
|
|
6911
6983
|
|
|
6912
6984
|
// Inner component that uses the Stripe hooks
|
|
6913
|
-
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
|
|
6985
|
+
function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
|
|
6914
6986
|
const t = useTranslations();
|
|
6915
|
-
const
|
|
6916
|
-
const
|
|
6917
|
-
|
|
6987
|
+
const isFreeform = Boolean(freeformSelection);
|
|
6988
|
+
const participantCount = isFreeform
|
|
6989
|
+
? 1
|
|
6990
|
+
: formData.participants.filter((p) => p.name.trim()).length;
|
|
6991
|
+
const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
|
|
6992
|
+
const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
|
|
6918
6993
|
return (jsxRuntime.jsx(StripeElementsPaymentForm, { onValidate: () => {
|
|
6994
|
+
if (isFreeform) {
|
|
6995
|
+
return null;
|
|
6996
|
+
}
|
|
6919
6997
|
const availableSpots = eventDetails.availableSpots || 0;
|
|
6920
6998
|
if (participantCount > availableSpots) {
|
|
6921
6999
|
return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
|
|
@@ -6937,7 +7015,7 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
|
|
|
6937
7015
|
},
|
|
6938
7016
|
}, submitContent: jsxRuntime.jsx(jsxRuntime.Fragment, { children: submitLabel }), loadingContent: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
|
|
6939
7017
|
}
|
|
6940
|
-
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
|
|
7018
|
+
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
|
|
6941
7019
|
const t = useTranslations();
|
|
6942
7020
|
const { locale } = useLocale();
|
|
6943
7021
|
const [clientSecret, setClientSecret] = React.useState(null);
|
|
@@ -6957,7 +7035,9 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
6957
7035
|
return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
|
|
6958
7036
|
}, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
|
|
6959
7037
|
const storageKey = typeof window !== "undefined"
|
|
6960
|
-
?
|
|
7038
|
+
? freeformSelection
|
|
7039
|
+
? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
|
|
7040
|
+
: `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
|
|
6961
7041
|
: "";
|
|
6962
7042
|
const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
|
|
6963
7043
|
function loadPersistedPaymentIntent() {
|
|
@@ -7012,48 +7092,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7012
7092
|
}, [paymentIntentId]);
|
|
7013
7093
|
React.useEffect(() => {
|
|
7014
7094
|
const createStripePayment = async () => {
|
|
7015
|
-
if (!systemConfig || !eventDetails
|
|
7095
|
+
if (!systemConfig || !eventDetails) {
|
|
7096
|
+
return;
|
|
7097
|
+
}
|
|
7098
|
+
if (freeformSelection) {
|
|
7099
|
+
if (!freeformSelection.customerEmail?.trim() ||
|
|
7100
|
+
!freeformSelection.customerName?.trim() ||
|
|
7101
|
+
!freeformSelection.start ||
|
|
7102
|
+
!freeformSelection.end) {
|
|
7103
|
+
return;
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
else if (!formData.participants?.length) {
|
|
7016
7107
|
return;
|
|
7017
7108
|
}
|
|
7018
7109
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
7019
|
-
if (
|
|
7110
|
+
if (!freeformSelection &&
|
|
7111
|
+
(participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
|
|
7020
7112
|
return;
|
|
7021
7113
|
}
|
|
7022
7114
|
setIsCreatingPaymentIntent(true);
|
|
7023
7115
|
setPaymentError(null);
|
|
7024
7116
|
try {
|
|
7025
|
-
const
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7117
|
+
const endpoint = freeformSelection
|
|
7118
|
+
? "/booking/create-freeform-stripe-payment"
|
|
7119
|
+
: "/booking/create-stripe-payment";
|
|
7120
|
+
const requestData = freeformSelection
|
|
7121
|
+
? {
|
|
7122
|
+
eventTypeId: freeformSelection.eventTypeId,
|
|
7123
|
+
organizationId: config.organizationId,
|
|
7124
|
+
start: freeformSelection.start,
|
|
7125
|
+
end: freeformSelection.end,
|
|
7126
|
+
requestedUnits: freeformSelection.requestedUnits,
|
|
7127
|
+
amount: Math.round(totalAmount),
|
|
7128
|
+
currency: "eur",
|
|
7129
|
+
customerName: freeformSelection.customerName.trim(),
|
|
7130
|
+
customerEmail: freeformSelection.customerEmail.trim(),
|
|
7131
|
+
customerPhone: freeformSelection.customerPhone?.trim(),
|
|
7132
|
+
comment: freeformSelection.comment?.trim(),
|
|
7133
|
+
locale,
|
|
7134
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7135
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7136
|
+
}
|
|
7137
|
+
: {
|
|
7138
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
7139
|
+
organizationId: config.organizationId,
|
|
7140
|
+
amount: Math.round(totalAmount),
|
|
7141
|
+
currency: "eur",
|
|
7142
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
7143
|
+
discountCode: discountCode?.code,
|
|
7144
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
7145
|
+
customerName: formData.customerName?.trim(),
|
|
7146
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
7147
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
7148
|
+
comment: formData.comment?.trim(),
|
|
7149
|
+
...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
|
|
7150
|
+
...(paymentIntentId && { paymentIntentId }),
|
|
7151
|
+
...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
|
|
7152
|
+
};
|
|
7044
7153
|
if (!requestData.organizationId) {
|
|
7045
7154
|
throw new Error("Organization ID is required");
|
|
7046
7155
|
}
|
|
7047
|
-
if (
|
|
7156
|
+
if (requestData.amount === undefined || requestData.amount < 0) {
|
|
7048
7157
|
throw new Error("Valid amount is required");
|
|
7049
7158
|
}
|
|
7050
|
-
if (!requestData
|
|
7159
|
+
if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
|
|
7160
|
+
throw new Error("Event instance ID is required");
|
|
7161
|
+
}
|
|
7162
|
+
if (!freeformSelection &&
|
|
7163
|
+
"participants" in requestData &&
|
|
7164
|
+
(!requestData.participants || requestData.participants.length === 0)) {
|
|
7051
7165
|
throw new Error("At least one participant is required");
|
|
7052
7166
|
}
|
|
7053
7167
|
if (!requestData.customerEmail) {
|
|
7054
7168
|
throw new Error("Customer email is required");
|
|
7055
7169
|
}
|
|
7056
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl,
|
|
7170
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
|
|
7057
7171
|
method: "POST",
|
|
7058
7172
|
headers: createApiHeaders(config, locale),
|
|
7059
7173
|
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
@@ -7098,9 +7212,13 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7098
7212
|
giftCards,
|
|
7099
7213
|
config,
|
|
7100
7214
|
upsellSelections,
|
|
7215
|
+
freeformSelection,
|
|
7216
|
+
locale,
|
|
7101
7217
|
]);
|
|
7102
7218
|
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
7103
|
-
const isFullyCoveredByGiftCards =
|
|
7219
|
+
const isFullyCoveredByGiftCards = freeformSelection
|
|
7220
|
+
? false
|
|
7221
|
+
: isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
7104
7222
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
7105
7223
|
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
7106
7224
|
}
|
|
@@ -7139,7 +7257,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
7139
7257
|
setPaymentIntentId(null);
|
|
7140
7258
|
setClientSecret(null);
|
|
7141
7259
|
onSuccess(result);
|
|
7142
|
-
}, onError: onError }) }));
|
|
7260
|
+
}, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
|
|
7143
7261
|
}
|
|
7144
7262
|
|
|
7145
7263
|
const HOLD_DURATION_SECONDS = 15 * 60;
|
|
@@ -13399,20 +13517,22 @@ const sectionHeaderStyles = sectionStyles.header;
|
|
|
13399
13517
|
const labelStyles = formStyles.label;
|
|
13400
13518
|
const inputStyles = formStyles.input;
|
|
13401
13519
|
const errorTextStyles = formStyles.error;
|
|
13402
|
-
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13520
|
+
function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
|
|
13403
13521
|
const t = useTranslations();
|
|
13404
13522
|
const { locale } = useLocale();
|
|
13405
13523
|
const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
|
|
13406
13524
|
// 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
13525
|
const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
|
|
13410
13526
|
const canUseEventVoucherType = voucherConfig.allowEventVouchers;
|
|
13411
|
-
const
|
|
13527
|
+
const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
|
|
13528
|
+
const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
|
|
13529
|
+
const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
|
|
13412
13530
|
const [voucherType, setVoucherType] = React.useState(canUseMonetaryVoucherType ? "monetary" : "event");
|
|
13413
13531
|
const [monetaryAmount, setMonetaryAmount] = React.useState(voucherConfig.monetaryPresets[2] || 10000);
|
|
13414
13532
|
const [selectedEventTypeId, setSelectedEventTypeId] = React.useState(null);
|
|
13415
13533
|
const [eventQuantity, setEventQuantity] = React.useState(1);
|
|
13534
|
+
const [selectedCategoryId, setSelectedCategoryId] = React.useState(null);
|
|
13535
|
+
const [categoryAmount, setCategoryAmount] = React.useState(10000);
|
|
13416
13536
|
const [recipientName, setRecipientName] = React.useState("");
|
|
13417
13537
|
const [message, setMessage] = React.useState("");
|
|
13418
13538
|
const [customerName, setCustomerName] = React.useState("");
|
|
@@ -13472,6 +13592,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13472
13592
|
if (voucherType === "monetary") {
|
|
13473
13593
|
return monetaryAmount;
|
|
13474
13594
|
}
|
|
13595
|
+
else if (voucherType === "category") {
|
|
13596
|
+
return categoryAmount;
|
|
13597
|
+
}
|
|
13475
13598
|
else {
|
|
13476
13599
|
const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13477
13600
|
if (eventType) {
|
|
@@ -13479,7 +13602,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13479
13602
|
}
|
|
13480
13603
|
return 0;
|
|
13481
13604
|
}
|
|
13482
|
-
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
|
|
13605
|
+
}, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
|
|
13483
13606
|
const selectedEventType = React.useMemo(() => {
|
|
13484
13607
|
return eventTypes.find((et) => et.id === selectedEventTypeId);
|
|
13485
13608
|
}, [eventTypes, selectedEventTypeId]);
|
|
@@ -13488,6 +13611,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13488
13611
|
voucherType,
|
|
13489
13612
|
selectedEventTypeId,
|
|
13490
13613
|
eventQuantity,
|
|
13614
|
+
selectedCategoryId,
|
|
13615
|
+
categoryAmount,
|
|
13491
13616
|
recipientName: recipientName.trim(),
|
|
13492
13617
|
message: message.trim(),
|
|
13493
13618
|
customerName: customerName.trim(),
|
|
@@ -13497,6 +13622,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13497
13622
|
voucherType,
|
|
13498
13623
|
selectedEventTypeId,
|
|
13499
13624
|
eventQuantity,
|
|
13625
|
+
selectedCategoryId,
|
|
13626
|
+
categoryAmount,
|
|
13500
13627
|
recipientName,
|
|
13501
13628
|
message,
|
|
13502
13629
|
customerName,
|
|
@@ -13515,24 +13642,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13515
13642
|
return false;
|
|
13516
13643
|
if (voucherType === "event" && !selectedEventTypeId)
|
|
13517
13644
|
return false;
|
|
13645
|
+
if (voucherType === "category" && !selectedCategoryId)
|
|
13646
|
+
return false;
|
|
13518
13647
|
return true;
|
|
13519
|
-
}, [customerName, customerEmail, acceptTerms, totalAmount,
|
|
13648
|
+
}, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
|
|
13520
13649
|
const defaultMonetaryAmount = React.useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
|
|
13521
13650
|
const handleVoucherTypeChange = React.useCallback((nextType) => {
|
|
13522
13651
|
setVoucherType(nextType);
|
|
13523
13652
|
setPaymentError(null);
|
|
13524
13653
|
if (nextType === "event") {
|
|
13525
|
-
// Keep voucher type mutually exclusive: entering event flow resets amount selection state.
|
|
13526
13654
|
setMonetaryAmount(defaultMonetaryAmount);
|
|
13655
|
+
setSelectedCategoryId(null);
|
|
13656
|
+
setCategoryAmount(10000);
|
|
13527
13657
|
if (eventTypes.length === 1) {
|
|
13528
13658
|
setSelectedEventTypeId(eventTypes[0]?.id ?? null);
|
|
13529
13659
|
}
|
|
13530
13660
|
return;
|
|
13531
13661
|
}
|
|
13532
|
-
|
|
13662
|
+
if (nextType === "category") {
|
|
13663
|
+
setMonetaryAmount(defaultMonetaryAmount);
|
|
13664
|
+
setSelectedEventTypeId(null);
|
|
13665
|
+
setEventQuantity(1);
|
|
13666
|
+
if (categories.length === 1) {
|
|
13667
|
+
setSelectedCategoryId(categories[0]?.id ?? null);
|
|
13668
|
+
}
|
|
13669
|
+
return;
|
|
13670
|
+
}
|
|
13533
13671
|
setSelectedEventTypeId(null);
|
|
13534
13672
|
setEventQuantity(1);
|
|
13535
|
-
|
|
13673
|
+
setSelectedCategoryId(null);
|
|
13674
|
+
setCategoryAmount(10000);
|
|
13675
|
+
}, [defaultMonetaryAmount, eventTypes, categories]);
|
|
13536
13676
|
React.useEffect(() => {
|
|
13537
13677
|
if (!canUseMonetaryVoucherType && voucherType === "monetary") {
|
|
13538
13678
|
setVoucherType("event");
|
|
@@ -13573,9 +13713,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13573
13713
|
organizationId: config.organizationId,
|
|
13574
13714
|
amount: totalAmount,
|
|
13575
13715
|
currency: "eur",
|
|
13576
|
-
voucherType,
|
|
13577
|
-
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13716
|
+
voucherType: voucherType === "category" ? "monetary" : voucherType,
|
|
13717
|
+
monetaryValue: voucherType === "monetary" ? totalAmount
|
|
13718
|
+
: voucherType === "category" ? categoryAmount
|
|
13719
|
+
: undefined,
|
|
13578
13720
|
eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
|
|
13721
|
+
eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
|
|
13579
13722
|
eventAmount: voucherType === "event" ? eventQuantity : undefined,
|
|
13580
13723
|
recipientName: recipientName.trim() || undefined,
|
|
13581
13724
|
message: message.trim() || undefined,
|
|
@@ -13766,7 +13909,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13766
13909
|
fontSize: "13px",
|
|
13767
13910
|
color: "var(--bw-text-muted)",
|
|
13768
13911
|
marginTop: "4px",
|
|
13769
|
-
}, children: t("voucher.eventTypeDesc") })] }))
|
|
13912
|
+
}, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxRuntime.jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
|
|
13913
|
+
flex: 1,
|
|
13914
|
+
padding: "16px",
|
|
13915
|
+
borderRadius: "var(--bw-border-radius)",
|
|
13916
|
+
border: voucherType === "category"
|
|
13917
|
+
? "2px solid var(--bw-highlight-color)"
|
|
13918
|
+
: "1px solid var(--bw-border-color)",
|
|
13919
|
+
backgroundColor: voucherType === "category"
|
|
13920
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
13921
|
+
: "var(--bw-surface-color)",
|
|
13922
|
+
cursor: "pointer",
|
|
13923
|
+
fontFamily: "var(--bw-font-family)",
|
|
13924
|
+
transition: "all 0.2s ease",
|
|
13925
|
+
textAlign: "center",
|
|
13926
|
+
}, children: [jsxRuntime.jsx("div", { style: {
|
|
13927
|
+
fontWeight: 600,
|
|
13928
|
+
color: voucherType === "category"
|
|
13929
|
+
? "var(--bw-highlight-color)"
|
|
13930
|
+
: "var(--bw-text-color)",
|
|
13931
|
+
}, children: t("voucher.categoryType") }), jsxRuntime.jsx("div", { style: {
|
|
13932
|
+
fontSize: "13px",
|
|
13933
|
+
color: "var(--bw-text-muted)",
|
|
13934
|
+
marginTop: "4px",
|
|
13935
|
+
}, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsxRuntime.jsx("div", { style: {
|
|
13770
13936
|
display: "grid",
|
|
13771
13937
|
gridTemplateColumns: "repeat(3, 1fr)",
|
|
13772
13938
|
gap: "8px",
|
|
@@ -13821,7 +13987,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13821
13987
|
fontSize: "18px",
|
|
13822
13988
|
color: "var(--bw-highlight-color)",
|
|
13823
13989
|
fontFamily: "var(--bw-font-family)",
|
|
13824
|
-
}, children: formatCurrency(totalAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.
|
|
13990
|
+
}, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), jsxRuntime.jsx("div", { style: { marginTop: "12px" }, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), jsxRuntime.jsxs("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
|
|
13991
|
+
...inputStyles,
|
|
13992
|
+
cursor: categories.length === 1 ? "not-allowed" : "pointer",
|
|
13993
|
+
}, children: [categories.length > 1 && (jsxRuntime.jsx("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), jsxRuntime.jsx("div", { style: {
|
|
13994
|
+
display: "grid",
|
|
13995
|
+
gridTemplateColumns: "repeat(3, 1fr)",
|
|
13996
|
+
gap: "8px",
|
|
13997
|
+
marginTop: "4px",
|
|
13998
|
+
}, children: voucherConfig.monetaryPresets.map((preset) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
|
|
13999
|
+
padding: "10px",
|
|
14000
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14001
|
+
border: categoryAmount === preset
|
|
14002
|
+
? "2px solid var(--bw-highlight-color)"
|
|
14003
|
+
: "1px solid var(--bw-border-color)",
|
|
14004
|
+
backgroundColor: categoryAmount === preset
|
|
14005
|
+
? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
|
|
14006
|
+
: "var(--bw-surface-color)",
|
|
14007
|
+
cursor: "pointer",
|
|
14008
|
+
fontFamily: "var(--bw-font-family)",
|
|
14009
|
+
fontWeight: 600,
|
|
14010
|
+
fontSize: "14px",
|
|
14011
|
+
color: categoryAmount === preset
|
|
14012
|
+
? "var(--bw-highlight-color)"
|
|
14013
|
+
: "var(--bw-text-color)",
|
|
14014
|
+
transition: "all 0.2s ease",
|
|
14015
|
+
}, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsxRuntime.jsx("div", { style: {
|
|
14016
|
+
marginTop: "16px",
|
|
14017
|
+
padding: "12px",
|
|
14018
|
+
backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
|
|
14019
|
+
borderRadius: "var(--bw-border-radius)",
|
|
14020
|
+
border: "1px solid var(--bw-highlight-color)",
|
|
14021
|
+
}, children: jsxRuntime.jsxs("div", { style: {
|
|
14022
|
+
display: "flex",
|
|
14023
|
+
justifyContent: "space-between",
|
|
14024
|
+
alignItems: "center",
|
|
14025
|
+
}, children: [jsxRuntime.jsx("span", { style: {
|
|
14026
|
+
color: "var(--bw-text-color)",
|
|
14027
|
+
fontFamily: "var(--bw-font-family)",
|
|
14028
|
+
}, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsxRuntime.jsx("span", { style: {
|
|
14029
|
+
fontWeight: 700,
|
|
14030
|
+
fontSize: "18px",
|
|
14031
|
+
color: "var(--bw-highlight-color)",
|
|
14032
|
+
fontFamily: "var(--bw-font-family)",
|
|
14033
|
+
}, children: formatCurrency(categoryAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsxRuntime.jsx("p", { style: {
|
|
13825
14034
|
fontSize: "13px",
|
|
13826
14035
|
color: "var(--bw-text-muted)",
|
|
13827
14036
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -13854,7 +14063,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
|
|
|
13854
14063
|
fontFamily: "var(--bw-font-family)",
|
|
13855
14064
|
}, children: voucherType === "monetary"
|
|
13856
14065
|
? t("voucher.monetaryVoucher")
|
|
13857
|
-
:
|
|
14066
|
+
: voucherType === "category"
|
|
14067
|
+
? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
|
|
14068
|
+
: `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxRuntime.jsxs("div", { style: {
|
|
13858
14069
|
fontSize: "13px",
|
|
13859
14070
|
color: "var(--bw-text-muted)",
|
|
13860
14071
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -14130,7 +14341,7 @@ function VoucherAttachment({ onClick }) {
|
|
|
14130
14341
|
}, children: t("voucher.buyAsGift") })] }));
|
|
14131
14342
|
}
|
|
14132
14343
|
|
|
14133
|
-
function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14344
|
+
function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
|
|
14134
14345
|
if (!voucherConfig?.enabled) {
|
|
14135
14346
|
return null;
|
|
14136
14347
|
}
|
|
@@ -14141,7 +14352,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
|
|
|
14141
14352
|
justifyContent: "center",
|
|
14142
14353
|
}, children: jsxRuntime.jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
|
|
14143
14354
|
? 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 }))] }));
|
|
14355
|
+
: undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, categories: categories, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
|
|
14145
14356
|
}
|
|
14146
14357
|
|
|
14147
14358
|
// Helper function to preprocess markdown for underline support
|
|
@@ -15409,6 +15620,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
|
|
|
15409
15620
|
}) })] }));
|
|
15410
15621
|
}
|
|
15411
15622
|
|
|
15623
|
+
function toMinutesForUnit(unit) {
|
|
15624
|
+
switch (unit.toLowerCase()) {
|
|
15625
|
+
case "minute":
|
|
15626
|
+
case "minutes":
|
|
15627
|
+
return 1;
|
|
15628
|
+
case "hour":
|
|
15629
|
+
case "hours":
|
|
15630
|
+
return 60;
|
|
15631
|
+
case "day":
|
|
15632
|
+
case "days":
|
|
15633
|
+
return 24 * 60;
|
|
15634
|
+
default:
|
|
15635
|
+
return 1;
|
|
15636
|
+
}
|
|
15637
|
+
}
|
|
15638
|
+
// --- Timezone helpers ------------------------------------------------------
|
|
15639
|
+
// The booking is evaluated server-side against the organization's local
|
|
15640
|
+
// operating hours. The customer's browser may be in a different timezone, so we
|
|
15641
|
+
// interpret the datetime-local field as a wall-clock time in the ORG timezone
|
|
15642
|
+
// (not the browser's) and convert to a UTC instant for the API.
|
|
15643
|
+
function getTzParts(date, timeZone) {
|
|
15644
|
+
const dtf = new Intl.DateTimeFormat("en-CA", {
|
|
15645
|
+
timeZone,
|
|
15646
|
+
hour12: false,
|
|
15647
|
+
year: "numeric",
|
|
15648
|
+
month: "2-digit",
|
|
15649
|
+
day: "2-digit",
|
|
15650
|
+
hour: "2-digit",
|
|
15651
|
+
minute: "2-digit",
|
|
15652
|
+
second: "2-digit",
|
|
15653
|
+
});
|
|
15654
|
+
const parts = {};
|
|
15655
|
+
for (const part of dtf.formatToParts(date)) {
|
|
15656
|
+
if (part.type !== "literal")
|
|
15657
|
+
parts[part.type] = part.value;
|
|
15658
|
+
}
|
|
15659
|
+
return parts;
|
|
15660
|
+
}
|
|
15661
|
+
function tzOffsetMs(utcMs, timeZone) {
|
|
15662
|
+
const p = getTzParts(new Date(utcMs), timeZone);
|
|
15663
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15664
|
+
const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
|
|
15665
|
+
return asLocalUtc - utcMs;
|
|
15666
|
+
}
|
|
15667
|
+
/** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
|
|
15668
|
+
function zonedWallTimeToUtc(wallTime, timeZone) {
|
|
15669
|
+
const [datePart, timePart] = wallTime.split("T");
|
|
15670
|
+
if (!datePart || !timePart)
|
|
15671
|
+
return new Date(NaN);
|
|
15672
|
+
const [y, mo, d] = datePart.split("-").map(Number);
|
|
15673
|
+
const [h, mi] = timePart.split(":").map(Number);
|
|
15674
|
+
const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
|
|
15675
|
+
// Single-pass offset correction (good enough outside the ~1h DST fold).
|
|
15676
|
+
const offset = tzOffsetMs(naiveUtc, timeZone);
|
|
15677
|
+
return new Date(naiveUtc - offset);
|
|
15678
|
+
}
|
|
15679
|
+
/** Render a UTC instant as an org-tz datetime-local input value. */
|
|
15680
|
+
function utcToZonedInputValue(date, timeZone) {
|
|
15681
|
+
const p = getTzParts(date, timeZone);
|
|
15682
|
+
const hour = p.hour === "24" ? "00" : p.hour;
|
|
15683
|
+
return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
|
|
15684
|
+
}
|
|
15685
|
+
/** Format a UTC instant for display in the org timezone. */
|
|
15686
|
+
function formatInTimeZone(date, timeZone, locale) {
|
|
15687
|
+
return new Intl.DateTimeFormat(locale, {
|
|
15688
|
+
timeZone,
|
|
15689
|
+
dateStyle: "medium",
|
|
15690
|
+
timeStyle: "short",
|
|
15691
|
+
}).format(date);
|
|
15692
|
+
}
|
|
15693
|
+
function calculateTieredPrice(durationUnits, config) {
|
|
15694
|
+
const tiers = [...(config.tiers ?? [])]
|
|
15695
|
+
.filter((tier) => tier.unitCount > 0 && tier.price >= 0)
|
|
15696
|
+
.sort((a, b) => b.unitCount - a.unitCount);
|
|
15697
|
+
if (tiers.length === 0) {
|
|
15698
|
+
return 0;
|
|
15699
|
+
}
|
|
15700
|
+
let total = 0;
|
|
15701
|
+
let remaining = durationUnits;
|
|
15702
|
+
for (const tier of tiers) {
|
|
15703
|
+
const count = Math.floor(remaining / tier.unitCount);
|
|
15704
|
+
if (count <= 0)
|
|
15705
|
+
continue;
|
|
15706
|
+
total += count * tier.price;
|
|
15707
|
+
remaining -= count * tier.unitCount;
|
|
15708
|
+
}
|
|
15709
|
+
if (remaining > 0) {
|
|
15710
|
+
if (typeof config.perUnitPrice === "number") {
|
|
15711
|
+
total += remaining * config.perUnitPrice;
|
|
15712
|
+
}
|
|
15713
|
+
else {
|
|
15714
|
+
total += tiers[tiers.length - 1]?.price ?? 0;
|
|
15715
|
+
}
|
|
15716
|
+
}
|
|
15717
|
+
return total;
|
|
15718
|
+
}
|
|
15719
|
+
function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
|
|
15720
|
+
const t = useTranslations();
|
|
15721
|
+
const { locale } = useLocale();
|
|
15722
|
+
const freeformConfig = eventType.freeformConfig;
|
|
15723
|
+
const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
15724
|
+
const isNamedResource = eventType.resourceKind === "named";
|
|
15725
|
+
const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
|
|
15726
|
+
const now = React.useMemo(() => {
|
|
15727
|
+
const next = new Date();
|
|
15728
|
+
next.setMinutes(next.getMinutes() + 30, 0, 0);
|
|
15729
|
+
return next;
|
|
15730
|
+
}, []);
|
|
15731
|
+
const [startInput, setStartInput] = React.useState(utcToZonedInputValue(now, timeZone));
|
|
15732
|
+
const [duration, setDuration] = React.useState(Math.max(1, freeformConfig?.minDuration ?? 1));
|
|
15733
|
+
const [requestedUnits, setRequestedUnits] = React.useState(1);
|
|
15734
|
+
const [customerName, setCustomerName] = React.useState("");
|
|
15735
|
+
const [customerEmail, setCustomerEmail] = React.useState("");
|
|
15736
|
+
const [customerPhone, setCustomerPhone] = React.useState("");
|
|
15737
|
+
const [comment, setComment] = React.useState("");
|
|
15738
|
+
const [acceptTerms, setAcceptTerms] = React.useState(false);
|
|
15739
|
+
const [checkoutStep, setCheckoutStep] = React.useState("details");
|
|
15740
|
+
const [availability, setAvailability] = React.useState(null);
|
|
15741
|
+
const [availabilityError, setAvailabilityError] = React.useState(null);
|
|
15742
|
+
const [isCheckingAvailability, setIsCheckingAvailability] = React.useState(false);
|
|
15743
|
+
React.useEffect(() => {
|
|
15744
|
+
if (!isOpen)
|
|
15745
|
+
return;
|
|
15746
|
+
setCheckoutStep("details");
|
|
15747
|
+
}, [isOpen]);
|
|
15748
|
+
React.useEffect(() => {
|
|
15749
|
+
if (!freeformConfig)
|
|
15750
|
+
return;
|
|
15751
|
+
setDuration(Math.max(1, freeformConfig.minDuration));
|
|
15752
|
+
setRequestedUnits(1);
|
|
15753
|
+
setAvailability(null);
|
|
15754
|
+
setAvailabilityError(null);
|
|
15755
|
+
}, [freeformConfig?.resourceId]);
|
|
15756
|
+
const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
|
|
15757
|
+
const startDate = React.useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
|
|
15758
|
+
const endDate = React.useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
|
|
15759
|
+
React.useEffect(() => {
|
|
15760
|
+
if (!isOpen || !freeformConfig)
|
|
15761
|
+
return;
|
|
15762
|
+
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
|
|
15763
|
+
return;
|
|
15764
|
+
const timer = setTimeout(async () => {
|
|
15765
|
+
setIsCheckingAvailability(true);
|
|
15766
|
+
setAvailabilityError(null);
|
|
15767
|
+
try {
|
|
15768
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
|
|
15769
|
+
method: "POST",
|
|
15770
|
+
headers: createApiHeaders(config, locale),
|
|
15771
|
+
body: JSON.stringify(createRequestBody(config, {
|
|
15772
|
+
eventTypeId: eventType.id,
|
|
15773
|
+
requestedUnits,
|
|
15774
|
+
start: startDate.toISOString(),
|
|
15775
|
+
end: endDate.toISOString(),
|
|
15776
|
+
})),
|
|
15777
|
+
});
|
|
15778
|
+
const data = await response.json();
|
|
15779
|
+
if (!response.ok) {
|
|
15780
|
+
setAvailability(null);
|
|
15781
|
+
setAvailabilityError(data.error || t("error.loadBookingData"));
|
|
15782
|
+
return;
|
|
15783
|
+
}
|
|
15784
|
+
setAvailability({
|
|
15785
|
+
available: Boolean(data.available),
|
|
15786
|
+
freeUnits: Number(data.freeUnits ?? 0),
|
|
15787
|
+
capacityUnits: Number(data.capacityUnits ?? 0),
|
|
15788
|
+
usedUnits: Number(data.usedUnits ?? 0),
|
|
15789
|
+
reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
|
|
15790
|
+
blockUsedUnits: Number(data.blockUsedUnits ?? 0),
|
|
15791
|
+
durationUnits: Number(data.durationUnits ?? 0),
|
|
15792
|
+
roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
|
|
15793
|
+
});
|
|
15794
|
+
}
|
|
15795
|
+
catch (_error) {
|
|
15796
|
+
setAvailability(null);
|
|
15797
|
+
setAvailabilityError(t("error.loadBookingData"));
|
|
15798
|
+
}
|
|
15799
|
+
finally {
|
|
15800
|
+
setIsCheckingAvailability(false);
|
|
15801
|
+
}
|
|
15802
|
+
}, 350);
|
|
15803
|
+
return () => clearTimeout(timer);
|
|
15804
|
+
}, [
|
|
15805
|
+
config,
|
|
15806
|
+
endDate,
|
|
15807
|
+
eventType.id,
|
|
15808
|
+
freeformConfig,
|
|
15809
|
+
isOpen,
|
|
15810
|
+
locale,
|
|
15811
|
+
requestedUnits,
|
|
15812
|
+
startDate,
|
|
15813
|
+
t,
|
|
15814
|
+
]);
|
|
15815
|
+
const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
|
|
15816
|
+
const basePrice = React.useMemo(() => {
|
|
15817
|
+
if (!freeformConfig)
|
|
15818
|
+
return 0;
|
|
15819
|
+
if (freeformConfig.pricingModel === "perUnit") {
|
|
15820
|
+
return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
|
|
15821
|
+
}
|
|
15822
|
+
return calculateTieredPrice(billedDurationUnits, freeformConfig);
|
|
15823
|
+
}, [billedDurationUnits, freeformConfig]);
|
|
15824
|
+
const totalPrice = Math.max(0, basePrice * requestedUnits);
|
|
15825
|
+
const canContinueToPayment = !!freeformConfig &&
|
|
15826
|
+
customerName.trim().length >= 2 &&
|
|
15827
|
+
customerEmail.trim().length > 3 &&
|
|
15828
|
+
acceptTerms &&
|
|
15829
|
+
!!availability?.available &&
|
|
15830
|
+
!isCheckingAvailability;
|
|
15831
|
+
const freeformSelection = React.useMemo(() => ({
|
|
15832
|
+
eventTypeId: eventType.id,
|
|
15833
|
+
start: startDate.toISOString(),
|
|
15834
|
+
end: endDate.toISOString(),
|
|
15835
|
+
requestedUnits,
|
|
15836
|
+
customerName: customerName.trim(),
|
|
15837
|
+
customerEmail: customerEmail.trim(),
|
|
15838
|
+
customerPhone: customerPhone.trim(),
|
|
15839
|
+
comment: comment.trim(),
|
|
15840
|
+
}), [
|
|
15841
|
+
comment,
|
|
15842
|
+
customerEmail,
|
|
15843
|
+
customerName,
|
|
15844
|
+
customerPhone,
|
|
15845
|
+
endDate,
|
|
15846
|
+
eventType.id,
|
|
15847
|
+
requestedUnits,
|
|
15848
|
+
startDate,
|
|
15849
|
+
]);
|
|
15850
|
+
const paymentFormData = React.useMemo(() => ({
|
|
15851
|
+
customerName: customerName.trim(),
|
|
15852
|
+
customerEmail: customerEmail.trim(),
|
|
15853
|
+
customerPhone: customerPhone.trim(),
|
|
15854
|
+
participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
|
|
15855
|
+
comment: comment.trim(),
|
|
15856
|
+
}), [comment, customerEmail, customerName, customerPhone]);
|
|
15857
|
+
const eventDetailsForPayment = React.useMemo(() => ({
|
|
15858
|
+
id: eventType.id,
|
|
15859
|
+
name: eventType.name,
|
|
15860
|
+
startTime: startDate.toISOString(),
|
|
15861
|
+
endTime: endDate.toISOString(),
|
|
15862
|
+
price: basePrice,
|
|
15863
|
+
maxParticipants: availability?.capacityUnits ?? requestedUnits,
|
|
15864
|
+
participantCount: 0,
|
|
15865
|
+
availableSpots: availability?.freeUnits ?? requestedUnits,
|
|
15866
|
+
durationDays: 1,
|
|
15867
|
+
durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
|
|
15868
|
+
images: eventType.images ?? [],
|
|
15869
|
+
category: eventType.category,
|
|
15870
|
+
organization: {
|
|
15871
|
+
id: config.organizationId,
|
|
15872
|
+
name: "",
|
|
15873
|
+
},
|
|
15874
|
+
bookingOpen: availability?.available ?? false,
|
|
15875
|
+
}), [
|
|
15876
|
+
availability?.available,
|
|
15877
|
+
availability?.capacityUnits,
|
|
15878
|
+
availability?.freeUnits,
|
|
15879
|
+
availability?.roundedDurationMinutes,
|
|
15880
|
+
basePrice,
|
|
15881
|
+
config.organizationId,
|
|
15882
|
+
duration,
|
|
15883
|
+
endDate,
|
|
15884
|
+
eventType.category,
|
|
15885
|
+
eventType.id,
|
|
15886
|
+
eventType.images,
|
|
15887
|
+
eventType.name,
|
|
15888
|
+
requestedUnits,
|
|
15889
|
+
startDate,
|
|
15890
|
+
unitMinutes,
|
|
15891
|
+
]);
|
|
15892
|
+
const footer = (jsxRuntime.jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsxRuntime.jsx("button", { type: "button", onClick: () => {
|
|
15893
|
+
if (checkoutStep === "payment") {
|
|
15894
|
+
setCheckoutStep("details");
|
|
15895
|
+
}
|
|
15896
|
+
else {
|
|
15897
|
+
onClose();
|
|
15898
|
+
}
|
|
15899
|
+
}, style: {
|
|
15900
|
+
flex: 1,
|
|
15901
|
+
padding: "12px 16px",
|
|
15902
|
+
border: "1px solid var(--bw-border-color)",
|
|
15903
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15904
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
15905
|
+
color: "var(--bw-text-color)",
|
|
15906
|
+
fontFamily: "var(--bw-font-family)",
|
|
15907
|
+
fontWeight: 600,
|
|
15908
|
+
cursor: "pointer",
|
|
15909
|
+
}, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsxRuntime.jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
|
|
15910
|
+
flex: 1,
|
|
15911
|
+
padding: "12px 16px",
|
|
15912
|
+
border: "none",
|
|
15913
|
+
borderRadius: "var(--bw-border-radius)",
|
|
15914
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
15915
|
+
color: "var(--bw-button-text-color, #ffffff)",
|
|
15916
|
+
fontFamily: "var(--bw-font-family)",
|
|
15917
|
+
fontWeight: 700,
|
|
15918
|
+
cursor: canContinueToPayment ? "pointer" : "not-allowed",
|
|
15919
|
+
opacity: canContinueToPayment ? 1 : 0.6,
|
|
15920
|
+
}, children: t("button.continueToPayment") }))] }));
|
|
15921
|
+
if (!freeformConfig) {
|
|
15922
|
+
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: jsxRuntime.jsx("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
|
|
15923
|
+
}
|
|
15924
|
+
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: jsxRuntime.jsx("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), jsxRuntime.jsx("input", { type: "datetime-local", value: startInput, onChange: (event) => setStartInput(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), jsxRuntime.jsx("input", { type: "number", min: freeformConfig.minDuration, max: freeformConfig.maxDuration, step: freeformConfig.stepDuration, value: duration, onChange: (event) => setDuration(Number(event.target.value)), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), !isNamedResource && (jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), jsxRuntime.jsx("input", { type: "number", min: 1, max: maxUnits, value: requestedUnits, onChange: (event) => setRequestedUnits(Math.min(maxUnits, Math.max(1, Number(event.target.value)))), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }))] }), jsxRuntime.jsxs("div", { style: { marginTop: "12px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: [formatInTimeZone(startDate, timeZone, locale), " - ", formatInTimeZone(endDate, timeZone, locale), eventType.organizationTimezone ? ` (${timeZone})` : ""] }), isCheckingAvailability && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: availability.available ? "var(--bw-success-color)" : "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availability.available
|
|
15925
|
+
? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
|
|
15926
|
+
: t("booking.freeformNotAvailable") }))] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsx("input", { type: "text", placeholder: t("booking.namePlaceholder"), value: customerName, onChange: (event) => setCustomerName(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("input", { type: "email", placeholder: t("booking.emailPlaceholder"), value: customerEmail, onChange: (event) => setCustomerEmail(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("input", { type: "tel", placeholder: t("booking.phonePlaceholder"), value: customerPhone, onChange: (event) => setCustomerPhone(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), jsxRuntime.jsx("textarea", { placeholder: t("booking.commentPlaceholder"), value: comment, onChange: (event) => setComment(event.target.value), rows: 3, style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)", resize: "vertical" } }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { children: t("booking.duration") }), jsxRuntime.jsx("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [jsxRuntime.jsx("span", { children: t("booking.price") }), jsxRuntime.jsxs("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-color)", fontFamily: "var(--bw-font-family)", fontSize: "20px", fontWeight: 700, marginTop: "10px" }, children: [jsxRuntime.jsx("span", { children: t("summary.totalAmount") }), jsxRuntime.jsx("span", { children: formatCurrency(totalPrice) })] })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(HoldCountdown, {}), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (jsxRuntime.jsx("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode, freeformSelection: freeformSelection })), systemConfig?.paymentProvider === "stripe" && (jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
|
|
15927
|
+
}
|
|
15928
|
+
|
|
15412
15929
|
const getThemeConfig = (theme = "generic") => {
|
|
15413
15930
|
switch (theme) {
|
|
15414
15931
|
case "christmas":
|
|
@@ -16049,6 +16566,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16049
16566
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
|
|
16050
16567
|
const [shouldRenderUpsells, setShouldRenderUpsells] = React.useState(false);
|
|
16051
16568
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = React.useState(false);
|
|
16569
|
+
const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = React.useState(false);
|
|
16052
16570
|
// Google Ads config (received from API, set once from the first API response)
|
|
16053
16571
|
const [googleAdsConfig, setGoogleAdsConfig] = React.useState(null);
|
|
16054
16572
|
const extractGoogleAdsConfig = (data) => {
|
|
@@ -16062,6 +16580,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16062
16580
|
// Voucher purchase state
|
|
16063
16581
|
const [voucherConfig, setVoucherConfig] = React.useState(null);
|
|
16064
16582
|
const [voucherEventTypes, setVoucherEventTypes] = React.useState([]);
|
|
16583
|
+
const [voucherCategories, setVoucherCategories] = React.useState([]);
|
|
16065
16584
|
const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = React.useState(false);
|
|
16066
16585
|
const [isVoucherFormOpen, setIsVoucherFormOpen] = React.useState(false);
|
|
16067
16586
|
const [voucherPurchaseResult, setVoucherPurchaseResult] = React.useState(null);
|
|
@@ -16141,6 +16660,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16141
16660
|
setVoucherConfig(mergedConfig);
|
|
16142
16661
|
extractGoogleAdsConfig(data);
|
|
16143
16662
|
setVoucherEventTypes(data.eventTypes || []);
|
|
16663
|
+
setVoucherCategories(data.categories || []);
|
|
16144
16664
|
// Set system config for payment processing
|
|
16145
16665
|
if (data.paymentProvider) {
|
|
16146
16666
|
setSystemConfig({
|
|
@@ -16169,7 +16689,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16169
16689
|
if (!analyticsInitRef.current && config.organizationId) {
|
|
16170
16690
|
analyticsInitRef.current = true;
|
|
16171
16691
|
initAnalytics(config.apiBaseUrl, config.organizationId, {
|
|
16172
|
-
partnerContractId: config.partnerContractId,
|
|
16692
|
+
...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
|
|
16173
16693
|
});
|
|
16174
16694
|
trackEvent("widget_loaded", {
|
|
16175
16695
|
viewMode,
|
|
@@ -16648,6 +17168,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16648
17168
|
const handleEventTypeSelect = async (eventType) => {
|
|
16649
17169
|
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16650
17170
|
setSelectedEventType(eventType);
|
|
17171
|
+
if (eventType.bookingMode === "freeform") {
|
|
17172
|
+
setCurrentStep("freeform");
|
|
17173
|
+
setShouldRenderFreeformSelection(true);
|
|
17174
|
+
setIsLoadingEventInstances(true);
|
|
17175
|
+
try {
|
|
17176
|
+
await loadEventInstances(eventType.id);
|
|
17177
|
+
}
|
|
17178
|
+
finally {
|
|
17179
|
+
setIsLoadingEventInstances(false);
|
|
17180
|
+
}
|
|
17181
|
+
return;
|
|
17182
|
+
}
|
|
16651
17183
|
setCurrentStep("eventInstances");
|
|
16652
17184
|
setShouldRenderInstanceSelection(true);
|
|
16653
17185
|
setIsLoadingEventInstances(true);
|
|
@@ -16738,6 +17270,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16738
17270
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16739
17271
|
setSidebarOpen(false);
|
|
16740
17272
|
setShouldRenderBookingForm(false);
|
|
17273
|
+
setShouldRenderFreeformSelection(false);
|
|
16741
17274
|
setBookingPersistedState(null);
|
|
16742
17275
|
config.onSuccess?.(result);
|
|
16743
17276
|
};
|
|
@@ -17053,6 +17586,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17053
17586
|
setShouldRenderInstanceSelection(false);
|
|
17054
17587
|
setShouldRenderUpsells(false);
|
|
17055
17588
|
setShouldRenderBookingForm(false);
|
|
17589
|
+
setShouldRenderFreeformSelection(false);
|
|
17056
17590
|
setSelectedUpsells([]);
|
|
17057
17591
|
setUpsells([]);
|
|
17058
17592
|
setBookingPersistedState(null);
|
|
@@ -17074,6 +17608,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17074
17608
|
setShouldRenderInstanceSelection(false);
|
|
17075
17609
|
setShouldRenderUpsells(false);
|
|
17076
17610
|
setShouldRenderBookingForm(false);
|
|
17611
|
+
setShouldRenderFreeformSelection(false);
|
|
17077
17612
|
setSelectedUpsells([]);
|
|
17078
17613
|
setUpsells([]);
|
|
17079
17614
|
setBookingPersistedState(null);
|
|
@@ -17093,6 +17628,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17093
17628
|
setSuccessPaymentId(null);
|
|
17094
17629
|
setShouldRenderInstanceSelection(false);
|
|
17095
17630
|
setShouldRenderBookingForm(false);
|
|
17631
|
+
setShouldRenderFreeformSelection(false);
|
|
17096
17632
|
const url = new URL(window.location.href);
|
|
17097
17633
|
url.searchParams.delete("payment_intent");
|
|
17098
17634
|
url.searchParams.delete("payment_intent_client_secret");
|
|
@@ -17136,13 +17672,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17136
17672
|
setCurrentStep("booking");
|
|
17137
17673
|
setShouldRenderBookingForm(true);
|
|
17138
17674
|
}
|
|
17675
|
+
else if (selectedEventType?.bookingMode === "freeform") {
|
|
17676
|
+
setCurrentStep("freeform");
|
|
17677
|
+
setShouldRenderFreeformSelection(true);
|
|
17678
|
+
}
|
|
17139
17679
|
else {
|
|
17140
17680
|
setCurrentStep("eventInstances");
|
|
17141
17681
|
setSidebarOpen(true);
|
|
17142
17682
|
setShouldRenderInstanceSelection(true);
|
|
17143
17683
|
}
|
|
17144
17684
|
}, children: [isButtonBusy && jsxRuntime.jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
|
|
17145
|
-
(isDirectInstanceMode
|
|
17685
|
+
(isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
|
|
17686
|
+
? t("button.bookNow")
|
|
17687
|
+
: t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
|
|
17688
|
+
setCurrentStep("eventTypes");
|
|
17689
|
+
setSidebarOpen(false);
|
|
17690
|
+
setShouldRenderFreeformSelection(false);
|
|
17691
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
17146
17692
|
setIsSuccess(false);
|
|
17147
17693
|
setCurrentStep("eventTypes");
|
|
17148
17694
|
setSidebarOpen(false);
|
|
@@ -17150,6 +17696,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17150
17696
|
setShouldRenderInstanceSelection(false);
|
|
17151
17697
|
setShouldRenderUpsells(false);
|
|
17152
17698
|
setShouldRenderBookingForm(false);
|
|
17699
|
+
setShouldRenderFreeformSelection(false);
|
|
17153
17700
|
setSelectedUpsells([]);
|
|
17154
17701
|
setUpsells([]);
|
|
17155
17702
|
const url = new URL(window.location.href);
|
|
@@ -17162,7 +17709,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17162
17709
|
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
17163
17710
|
}
|
|
17164
17711
|
// 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: {
|
|
17712
|
+
const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
|
|
17166
17713
|
display: "inline-block",
|
|
17167
17714
|
width: "32px",
|
|
17168
17715
|
height: "32px",
|
|
@@ -17203,13 +17750,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17203
17750
|
};
|
|
17204
17751
|
};
|
|
17205
17752
|
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(
|
|
17753
|
+
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
|
|
17754
|
+
setCurrentStep("eventTypes");
|
|
17755
|
+
setShouldRenderFreeformSelection(false);
|
|
17756
|
+
}, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
|
|
17207
17757
|
setIsSuccess(false);
|
|
17208
17758
|
setCurrentStep("eventTypes");
|
|
17209
17759
|
setSuccessPaymentId(null);
|
|
17210
17760
|
setShouldRenderInstanceSelection(false);
|
|
17211
17761
|
setShouldRenderUpsells(false);
|
|
17212
17762
|
setShouldRenderBookingForm(false);
|
|
17763
|
+
setShouldRenderFreeformSelection(false);
|
|
17213
17764
|
setSelectedUpsells([]);
|
|
17214
17765
|
setUpsells([]);
|
|
17215
17766
|
const url = new URL(window.location.href);
|
|
@@ -17219,7 +17770,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
17219
17770
|
url.searchParams.delete("mollie_payment_id");
|
|
17220
17771
|
url.searchParams.delete("mollie_status");
|
|
17221
17772
|
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: () => {
|
|
17773
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
|
|
17223
17774
|
setIsSuccess(false);
|
|
17224
17775
|
setVoucherPurchaseResult(null);
|
|
17225
17776
|
const url = new URL(window.location.href);
|
|
@@ -17267,7 +17818,7 @@ function styleInject(css, ref) {
|
|
|
17267
17818
|
}
|
|
17268
17819
|
}
|
|
17269
17820
|
|
|
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}}";
|
|
17821
|
+
var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.booking-widget-container .bw-btn{letter-spacing:var(--bw-letter-spacing,normal);text-transform:var(--bw-text-transform,none);transition:all .2s ease!important}.booking-widget-container .bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.booking-widget-container .bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.booking-widget-container .bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.booking-widget-container .bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.booking-widget-container .bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.booking-widget-container .bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.booking-widget-container .bw-btn:disabled,.booking-widget-container .bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}.booking-widget-container button[class*=bw-btn],.booking-widget-container button[style*=transition]{transition:all .2s ease!important}.booking-widget-container button[data-variant=primary]:hover:not(:disabled),.booking-widget-container button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}.booking-widget-container button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}.booking-widget-container button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}.booking-widget-container button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.booking-widget-container .bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.booking-widget-container .bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
|
|
17271
17822
|
styleInject(css_248z);
|
|
17272
17823
|
|
|
17273
17824
|
// Export init function for vanilla JS usage
|