@bigz-app/booking-widget 1.1.6 → 1.1.8
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/README.md +3 -1
- package/dist/booking-widget.js +176 -181
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- package/dist/components/booking/BookingForm.d.ts +0 -1
- package/dist/components/booking/BookingForm.d.ts.map +1 -1
- package/dist/components/booking/BookingSuccessModal.d.ts.map +1 -1
- package/dist/components/booking/GiftCardOnlyBooking.d.ts +22 -0
- package/dist/components/booking/GiftCardOnlyBooking.d.ts.map +1 -0
- package/dist/components/booking/MolliePaymentForm.d.ts +2 -6
- package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
- package/dist/components/booking/{PaymentForm.d.ts → StripePaymentForm.d.ts} +4 -8
- package/dist/components/booking/StripePaymentForm.d.ts.map +1 -0
- package/dist/components/booking/VoucherInput.d.ts +8 -0
- package/dist/components/booking/VoucherInput.d.ts.map +1 -1
- package/dist/components/booking/index.d.ts +2 -1
- package/dist/components/booking/index.d.ts.map +1 -1
- package/dist/components/upsells/UpsellCard.d.ts +1 -2
- package/dist/components/upsells/UpsellCard.d.ts.map +1 -1
- package/dist/components/upsells/UpsellsStep.d.ts +1 -4
- package/dist/components/upsells/UpsellsStep.d.ts.map +1 -1
- package/dist/components/upsells/index.d.ts +1 -1
- package/dist/components/upsells/index.d.ts.map +1 -1
- package/dist/index.cjs +176 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +176 -181
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/booking/PaymentForm.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -223,7 +223,7 @@ const de$1 = {
|
|
|
223
223
|
"error.loadBookingData": "Fehler beim Abrufen der Buchungsdaten",
|
|
224
224
|
"error.processingError": "Ein Fehler ist bei der Verarbeitung aufgetreten.",
|
|
225
225
|
"error.createBooking": "Fehler beim Erstellen der Buchung",
|
|
226
|
-
"error.
|
|
226
|
+
"error.createPayment": "Fehler beim Erstellen der Zahlung",
|
|
227
227
|
"error.paymentProcessing": "Fehler beim Verarbeiten der Zahlung",
|
|
228
228
|
"error.paymentFailed": "Die Zahlung war nicht erfolgreich. Bitte versuche es erneut.",
|
|
229
229
|
"error.paymentIncomplete": "Die Zahlung konnte nicht abgeschlossen werden. Bitte versuche es erneut.",
|
|
@@ -337,7 +337,7 @@ const de$1 = {
|
|
|
337
337
|
"voucher.giftCardApplied": "−{{amount}} Gutschein",
|
|
338
338
|
"voucher.remaining": "Rest: {{amount}}",
|
|
339
339
|
"voucher.remove": "Entfernen",
|
|
340
|
-
"voucher.alreadyHasDiscount": "
|
|
340
|
+
"voucher.alreadyHasDiscount": "Du kannst weitere Gutscheine hinzufügen.",
|
|
341
341
|
// Booking success
|
|
342
342
|
"success.title": "Reservierung erfolgreich!",
|
|
343
343
|
"success.bookingDetails": "Buchungsdetails",
|
|
@@ -432,7 +432,7 @@ const en = {
|
|
|
432
432
|
"error.loadBookingData": "Error fetching booking data",
|
|
433
433
|
"error.processingError": "An error occurred during processing.",
|
|
434
434
|
"error.createBooking": "Error creating booking",
|
|
435
|
-
"error.
|
|
435
|
+
"error.createPayment": "Error creating payment",
|
|
436
436
|
"error.paymentProcessing": "Error processing payment",
|
|
437
437
|
"error.paymentFailed": "The payment was not successful. Please try again.",
|
|
438
438
|
"error.paymentIncomplete": "The payment could not be completed. Please try again.",
|
|
@@ -641,7 +641,7 @@ const es = {
|
|
|
641
641
|
"error.loadBookingData": "Error al obtener datos de la reserva",
|
|
642
642
|
"error.processingError": "Ocurrió un error durante el procesamiento.",
|
|
643
643
|
"error.createBooking": "Error al crear la reserva",
|
|
644
|
-
"error.
|
|
644
|
+
"error.createPayment": "Error al crear el pago",
|
|
645
645
|
"error.paymentProcessing": "Error al procesar el pago",
|
|
646
646
|
"error.paymentFailed": "El pago no fue exitoso. Por favor, inténtalo de nuevo.",
|
|
647
647
|
"error.paymentIncomplete": "El pago no pudo completarse. Por favor, inténtalo de nuevo.",
|
|
@@ -850,7 +850,7 @@ const pt = {
|
|
|
850
850
|
"error.loadBookingData": "Erro ao obter dados da reserva",
|
|
851
851
|
"error.processingError": "Ocorreu um erro durante o processamento.",
|
|
852
852
|
"error.createBooking": "Erro ao criar reserva",
|
|
853
|
-
"error.
|
|
853
|
+
"error.createPayment": "Erro ao criar pagamento",
|
|
854
854
|
"error.paymentProcessing": "Erro ao processar pagamento",
|
|
855
855
|
"error.paymentFailed": "O pagamento não foi bem-sucedido. Por favor, tente novamente.",
|
|
856
856
|
"error.paymentIncomplete": "O pagamento não pôde ser concluído. Por favor, tente novamente.",
|
|
@@ -1059,7 +1059,7 @@ const sv = {
|
|
|
1059
1059
|
"error.loadBookingData": "Fel vid hämtning av bokningsdata",
|
|
1060
1060
|
"error.processingError": "Ett fel uppstod vid bearbetningen.",
|
|
1061
1061
|
"error.createBooking": "Fel vid skapande av bokning",
|
|
1062
|
-
"error.
|
|
1062
|
+
"error.createPayment": "Fel vid skapande av betalning",
|
|
1063
1063
|
"error.paymentProcessing": "Fel vid bearbetning av betalning",
|
|
1064
1064
|
"error.paymentFailed": "Betalningen lyckades inte. Försök igen.",
|
|
1065
1065
|
"error.paymentIncomplete": "Betalningen kunde inte slutföras. Försök igen.",
|
|
@@ -4418,6 +4418,108 @@ const IconChevronLeft = ({ size = 20, color = "white", className, }) => (jsxRunt
|
|
|
4418
4418
|
// Chevron Right icon - used for carousel navigation
|
|
4419
4419
|
const IconChevronRight = ({ size = 20, color = "white", className, }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: jsxRuntime.jsx("polyline", { points: "9 18 15 12 9 6" }) }));
|
|
4420
4420
|
|
|
4421
|
+
function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, upsellSelections = [], }) {
|
|
4422
|
+
const t = useTranslations();
|
|
4423
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
4424
|
+
const [error, setError] = React.useState(null);
|
|
4425
|
+
const handleBooking = async () => {
|
|
4426
|
+
setIsLoading(true);
|
|
4427
|
+
setError(null);
|
|
4428
|
+
try {
|
|
4429
|
+
const requestData = {
|
|
4430
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
4431
|
+
organizationId: config.organizationId,
|
|
4432
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
4433
|
+
discountCode: discountCode?.code,
|
|
4434
|
+
giftCardCodes: giftCards.map((gc) => gc.code),
|
|
4435
|
+
customerName: formData.customerName?.trim(),
|
|
4436
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
4437
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
4438
|
+
comment: formData.comment?.trim(),
|
|
4439
|
+
paymentMethod: "gift_card",
|
|
4440
|
+
...(upsellSelections.length > 0 && { upsellSelections }),
|
|
4441
|
+
};
|
|
4442
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
|
|
4443
|
+
method: "POST",
|
|
4444
|
+
headers: createApiHeaders(config),
|
|
4445
|
+
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
4446
|
+
});
|
|
4447
|
+
const data = await response.json();
|
|
4448
|
+
if (response.ok) {
|
|
4449
|
+
onSuccess({
|
|
4450
|
+
booking: data.booking,
|
|
4451
|
+
order: data.order,
|
|
4452
|
+
giftCardRedemptions: data.giftCardRedemptions,
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
else {
|
|
4456
|
+
setError(data.error || t("error.createBooking"));
|
|
4457
|
+
onError(data.error || t("error.createBooking"));
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
catch (err) {
|
|
4461
|
+
setError(err.message || t("error.createBooking"));
|
|
4462
|
+
onError(err.message || t("error.createBooking"));
|
|
4463
|
+
}
|
|
4464
|
+
finally {
|
|
4465
|
+
setIsLoading(false);
|
|
4466
|
+
}
|
|
4467
|
+
};
|
|
4468
|
+
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
4469
|
+
return (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [jsxRuntime.jsxs("div", { style: {
|
|
4470
|
+
backgroundColor: "rgba(var(--bw-success-color), 0.15)",
|
|
4471
|
+
border: "1px solid rgba(var(--bw-success-color), 0.4)",
|
|
4472
|
+
borderRadius: "var(--bw-border-radius)",
|
|
4473
|
+
padding: "16px",
|
|
4474
|
+
}, children: [jsxRuntime.jsxs("div", { style: {
|
|
4475
|
+
display: "flex",
|
|
4476
|
+
alignItems: "center",
|
|
4477
|
+
gap: "8px",
|
|
4478
|
+
marginBottom: "8px",
|
|
4479
|
+
color: "var(--bw-success-color)",
|
|
4480
|
+
fontFamily: "var(--bw-font-family)",
|
|
4481
|
+
fontWeight: 600,
|
|
4482
|
+
}, children: ["\uD83C\uDF81 ", t("payment.giftCardCovered")] }), jsxRuntime.jsx("div", { style: {
|
|
4483
|
+
fontSize: "16px",
|
|
4484
|
+
color: "var(--bw-text-muted)",
|
|
4485
|
+
fontFamily: "var(--bw-font-family)",
|
|
4486
|
+
}, children: t("payment.giftCardBalance", { amount: formatCurrency(totalGiftCardAmount) }) })] }), error && (jsxRuntime.jsxs("div", { style: {
|
|
4487
|
+
backgroundColor: "rgba(var(--bw-error-color), 0.15)",
|
|
4488
|
+
border: "1px solid rgba(var(--bw-error-color), 0.4)",
|
|
4489
|
+
borderRadius: "var(--bw-border-radius)",
|
|
4490
|
+
padding: "16px",
|
|
4491
|
+
color: "var(--bw-error-color)",
|
|
4492
|
+
fontSize: "16px",
|
|
4493
|
+
fontFamily: "var(--bw-font-family)",
|
|
4494
|
+
}, children: ["\u26A0\uFE0F ", error] })), jsxRuntime.jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
|
|
4495
|
+
width: "100%",
|
|
4496
|
+
padding: "12px 24px",
|
|
4497
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
4498
|
+
color: "#ffffff",
|
|
4499
|
+
border: "none",
|
|
4500
|
+
borderRadius: "var(--bw-border-radius)",
|
|
4501
|
+
fontSize: "16px",
|
|
4502
|
+
fontWeight: 600,
|
|
4503
|
+
fontFamily: "var(--bw-font-family)",
|
|
4504
|
+
transition: "all 0.2s ease",
|
|
4505
|
+
display: "flex",
|
|
4506
|
+
alignItems: "center",
|
|
4507
|
+
justifyContent: "center",
|
|
4508
|
+
gap: "8px",
|
|
4509
|
+
cursor: isLoading ? "not-allowed" : "pointer",
|
|
4510
|
+
opacity: isLoading ? 0.6 : 1,
|
|
4511
|
+
}, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("#ffffff"), t("button.creatingBooking")] })) : (t("button.bookWithGiftCard")) })] }));
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* Calculate whether gift cards fully cover the order after discounts.
|
|
4515
|
+
*/
|
|
4516
|
+
function isGiftCardFullyCovered(giftCards, eventPrice, participantCount, discountAmount) {
|
|
4517
|
+
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
4518
|
+
const baseTotal = eventPrice * participantCount;
|
|
4519
|
+
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
4520
|
+
return totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4421
4523
|
const mollieLocaleMap = {
|
|
4422
4524
|
de: "de_DE",
|
|
4423
4525
|
en: "en_US",
|
|
@@ -4478,13 +4580,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4478
4580
|
const mollieRef = React.useRef(null);
|
|
4479
4581
|
const cardFormRef = React.useRef(null);
|
|
4480
4582
|
const cardContainerRef = React.useRef(null);
|
|
4481
|
-
const
|
|
4482
|
-
const
|
|
4483
|
-
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
4484
|
-
: 0;
|
|
4485
|
-
const discountAmount = discountCode?.discountAmount || 0;
|
|
4486
|
-
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
4487
|
-
const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
4583
|
+
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
4584
|
+
const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
4488
4585
|
const isCreditCard = selectedMethod === "creditcard";
|
|
4489
4586
|
React.useEffect(() => {
|
|
4490
4587
|
if (isFullyCoveredByGiftCards)
|
|
@@ -4591,7 +4688,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4591
4688
|
};
|
|
4592
4689
|
}, [isCreditCard, mollieProfileId, mollieTestmode, isFullyCoveredByGiftCards]);
|
|
4593
4690
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
4594
|
-
return
|
|
4691
|
+
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
4595
4692
|
}
|
|
4596
4693
|
const validateBeforePayment = () => {
|
|
4597
4694
|
const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
|
|
@@ -4633,7 +4730,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4633
4730
|
}
|
|
4634
4731
|
const { token, error: tokenError } = await mollieRef.current.createToken();
|
|
4635
4732
|
if (tokenError || !token) {
|
|
4636
|
-
setPaymentError(tokenError?.message || t("error.
|
|
4733
|
+
setPaymentError(tokenError?.message || t("error.createPayment"));
|
|
4637
4734
|
return;
|
|
4638
4735
|
}
|
|
4639
4736
|
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
|
|
@@ -4649,8 +4746,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4649
4746
|
_onSuccess({ paymentIntent: { id: data.molliePaymentId } });
|
|
4650
4747
|
}
|
|
4651
4748
|
else {
|
|
4652
|
-
setPaymentError(data.error || t("error.
|
|
4653
|
-
onError(data.error || t("error.
|
|
4749
|
+
setPaymentError(data.error || t("error.createPayment"));
|
|
4750
|
+
onError(data.error || t("error.createPayment"));
|
|
4654
4751
|
}
|
|
4655
4752
|
}
|
|
4656
4753
|
catch (err) {
|
|
@@ -4682,8 +4779,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4682
4779
|
window.location.href = data.checkoutUrl;
|
|
4683
4780
|
}
|
|
4684
4781
|
else {
|
|
4685
|
-
setPaymentError(data.error || t("error.
|
|
4686
|
-
onError(data.error || t("error.
|
|
4782
|
+
setPaymentError(data.error || t("error.createPayment"));
|
|
4783
|
+
onError(data.error || t("error.createPayment"));
|
|
4687
4784
|
}
|
|
4688
4785
|
}
|
|
4689
4786
|
catch (err) {
|
|
@@ -6153,106 +6250,6 @@ var reactStripe_umd = {exports: {}};
|
|
|
6153
6250
|
|
|
6154
6251
|
var reactStripe_umdExports = reactStripe_umd.exports;
|
|
6155
6252
|
|
|
6156
|
-
// Component for bookings fully covered by gift cards (no Stripe payment needed)
|
|
6157
|
-
function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, upsellSelections = [], }) {
|
|
6158
|
-
const t = useTranslations();
|
|
6159
|
-
const [isLoading, setIsLoading] = React.useState(false);
|
|
6160
|
-
const [error, setError] = React.useState(null);
|
|
6161
|
-
const handleBooking = async () => {
|
|
6162
|
-
setIsLoading(true);
|
|
6163
|
-
setError(null);
|
|
6164
|
-
try {
|
|
6165
|
-
const requestData = {
|
|
6166
|
-
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
6167
|
-
organizationId: config.organizationId,
|
|
6168
|
-
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
6169
|
-
discountCode: discountCode?.code,
|
|
6170
|
-
giftCardCodes: giftCards.map((gc) => gc.code),
|
|
6171
|
-
customerName: formData.customerName?.trim(),
|
|
6172
|
-
customerEmail: formData.customerEmail?.trim(),
|
|
6173
|
-
customerPhone: formData.customerPhone?.trim(),
|
|
6174
|
-
comment: formData.comment?.trim(),
|
|
6175
|
-
paymentMethod: "gift_card",
|
|
6176
|
-
...(upsellSelections.length > 0 && { upsellSelections }),
|
|
6177
|
-
};
|
|
6178
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
|
|
6179
|
-
method: "POST",
|
|
6180
|
-
headers: createApiHeaders(config),
|
|
6181
|
-
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
6182
|
-
});
|
|
6183
|
-
const data = await response.json();
|
|
6184
|
-
if (response.ok) {
|
|
6185
|
-
onSuccess({
|
|
6186
|
-
booking: data.booking,
|
|
6187
|
-
order: data.order,
|
|
6188
|
-
giftCardRedemptions: data.giftCardRedemptions,
|
|
6189
|
-
});
|
|
6190
|
-
}
|
|
6191
|
-
else {
|
|
6192
|
-
setError(data.error || t("error.createBooking"));
|
|
6193
|
-
onError(data.error || t("error.createBooking"));
|
|
6194
|
-
}
|
|
6195
|
-
}
|
|
6196
|
-
catch (err) {
|
|
6197
|
-
setError(err.message || t("error.createBooking"));
|
|
6198
|
-
onError(err.message || t("error.createBooking"));
|
|
6199
|
-
}
|
|
6200
|
-
finally {
|
|
6201
|
-
setIsLoading(false);
|
|
6202
|
-
}
|
|
6203
|
-
};
|
|
6204
|
-
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
6205
|
-
return (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [jsxRuntime.jsxs("div", { style: {
|
|
6206
|
-
backgroundColor: "rgba(var(--bw-success-color), 0.15)",
|
|
6207
|
-
border: "1px solid rgba(var(--bw-success-color), 0.4)",
|
|
6208
|
-
borderRadius: "var(--bw-border-radius)",
|
|
6209
|
-
padding: "16px",
|
|
6210
|
-
}, children: [jsxRuntime.jsxs("div", { style: {
|
|
6211
|
-
display: "flex",
|
|
6212
|
-
alignItems: "center",
|
|
6213
|
-
gap: "8px",
|
|
6214
|
-
marginBottom: "8px",
|
|
6215
|
-
color: "var(--bw-success-color)",
|
|
6216
|
-
fontFamily: "var(--bw-font-family)",
|
|
6217
|
-
fontWeight: 600,
|
|
6218
|
-
}, children: ["\uD83C\uDF81 ", t("payment.giftCardCovered")] }), jsxRuntime.jsx("div", { style: {
|
|
6219
|
-
fontSize: "16px",
|
|
6220
|
-
color: "var(--bw-text-muted)",
|
|
6221
|
-
fontFamily: "var(--bw-font-family)",
|
|
6222
|
-
}, children: t("payment.giftCardBalance", { amount: formatCurrency(totalGiftCardAmount) }) })] }), error && (jsxRuntime.jsxs("div", { style: {
|
|
6223
|
-
backgroundColor: "rgba(var(--bw-error-color), 0.15)",
|
|
6224
|
-
border: "1px solid rgba(var(--bw-error-color), 0.4)",
|
|
6225
|
-
borderRadius: "var(--bw-border-radius)",
|
|
6226
|
-
padding: "16px",
|
|
6227
|
-
color: "var(--bw-error-color)",
|
|
6228
|
-
fontSize: "16px",
|
|
6229
|
-
fontFamily: "var(--bw-font-family)",
|
|
6230
|
-
}, children: ["\u26A0\uFE0F ", error] })), jsxRuntime.jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
|
|
6231
|
-
width: "100%",
|
|
6232
|
-
padding: "12px 24px",
|
|
6233
|
-
backgroundColor: "var(--bw-highlight-color)",
|
|
6234
|
-
color: "#ffffff",
|
|
6235
|
-
border: "none",
|
|
6236
|
-
borderRadius: "var(--bw-border-radius)",
|
|
6237
|
-
fontSize: "16px",
|
|
6238
|
-
fontWeight: 600,
|
|
6239
|
-
fontFamily: "var(--bw-font-family)",
|
|
6240
|
-
transition: "all 0.2s ease",
|
|
6241
|
-
display: "flex",
|
|
6242
|
-
alignItems: "center",
|
|
6243
|
-
justifyContent: "center",
|
|
6244
|
-
gap: "8px",
|
|
6245
|
-
cursor: isLoading ? "not-allowed" : "pointer",
|
|
6246
|
-
opacity: isLoading ? 0.6 : 1,
|
|
6247
|
-
}, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: {
|
|
6248
|
-
width: "16px",
|
|
6249
|
-
height: "16px",
|
|
6250
|
-
border: "2px solid #ffffff",
|
|
6251
|
-
borderTopColor: "transparent",
|
|
6252
|
-
borderRadius: "50%",
|
|
6253
|
-
animation: "spin 1s linear infinite",
|
|
6254
|
-
} }), t("button.creatingBooking")] })) : (t("button.bookWithGiftCard")) })] }));
|
|
6255
|
-
}
|
|
6256
6253
|
// Inner component that uses the Stripe hooks
|
|
6257
6254
|
function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSuccess, onError, }) {
|
|
6258
6255
|
const t = useTranslations();
|
|
@@ -6289,7 +6286,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSucce
|
|
|
6289
6286
|
redirect: "if_required",
|
|
6290
6287
|
});
|
|
6291
6288
|
if (error) {
|
|
6292
|
-
console.error("[
|
|
6289
|
+
console.error("[STRIPE_PAYMENT] Payment confirmation error:", {
|
|
6293
6290
|
type: error.type,
|
|
6294
6291
|
code: error.code,
|
|
6295
6292
|
message: error.message,
|
|
@@ -6365,8 +6362,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSucce
|
|
|
6365
6362
|
? t("button.depositAndBook")
|
|
6366
6363
|
: t("button.bookNow") })) })] }));
|
|
6367
6364
|
}
|
|
6368
|
-
|
|
6369
|
-
function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
|
|
6365
|
+
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
|
|
6370
6366
|
const t = useTranslations();
|
|
6371
6367
|
const [clientSecret, setClientSecret] = React.useState(null);
|
|
6372
6368
|
const [paymentIntentId, setPaymentIntentId] = React.useState(null);
|
|
@@ -6427,7 +6423,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6427
6423
|
}
|
|
6428
6424
|
}, [paymentIntentId]);
|
|
6429
6425
|
React.useEffect(() => {
|
|
6430
|
-
const
|
|
6426
|
+
const createStripePayment = async () => {
|
|
6431
6427
|
if (!systemConfig || !eventDetails || !formData.participants?.length) {
|
|
6432
6428
|
return;
|
|
6433
6429
|
}
|
|
@@ -6468,35 +6464,38 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6468
6464
|
if (!requestData.customerEmail) {
|
|
6469
6465
|
throw new Error("Customer email is required");
|
|
6470
6466
|
}
|
|
6471
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-payment
|
|
6467
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
|
|
6472
6468
|
method: "POST",
|
|
6473
6469
|
headers: createApiHeaders(config),
|
|
6474
6470
|
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
6475
6471
|
});
|
|
6476
6472
|
const data = await response.json();
|
|
6477
6473
|
if (response.ok) {
|
|
6474
|
+
if (data.stalePaymentIntent) {
|
|
6475
|
+
clearPersistedPaymentIntent();
|
|
6476
|
+
}
|
|
6478
6477
|
setClientSecret(data.clientSecret);
|
|
6479
6478
|
setPaymentIntentId(data.paymentIntentId);
|
|
6480
6479
|
}
|
|
6481
6480
|
else {
|
|
6482
|
-
console.error("[
|
|
6481
|
+
console.error("[STRIPE_PAYMENT] Payment creation failed:", {
|
|
6483
6482
|
status: response.status,
|
|
6484
6483
|
error: data.error,
|
|
6485
6484
|
details: data.details,
|
|
6486
6485
|
requestData: { ...requestData, customerEmail: "[redacted]" },
|
|
6487
6486
|
});
|
|
6488
|
-
setPaymentError(data.error || t("error.
|
|
6487
|
+
setPaymentError(data.error || t("error.createPayment"));
|
|
6489
6488
|
}
|
|
6490
6489
|
}
|
|
6491
6490
|
catch (err) {
|
|
6492
|
-
console.error("[
|
|
6493
|
-
setPaymentError(err.message || t("error.
|
|
6491
|
+
console.error("[STRIPE_PAYMENT] Payment creation error:", err);
|
|
6492
|
+
setPaymentError(err.message || t("error.createPayment"));
|
|
6494
6493
|
}
|
|
6495
6494
|
finally {
|
|
6496
6495
|
setIsCreatingPaymentIntent(false);
|
|
6497
6496
|
}
|
|
6498
6497
|
};
|
|
6499
|
-
const timer = setTimeout(
|
|
6498
|
+
const timer = setTimeout(createStripePayment, 500);
|
|
6500
6499
|
return () => clearTimeout(timer);
|
|
6501
6500
|
}, [
|
|
6502
6501
|
systemConfig,
|
|
@@ -6504,19 +6503,15 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6504
6503
|
formData.participants,
|
|
6505
6504
|
formData.customerEmail,
|
|
6506
6505
|
formData.customerName,
|
|
6506
|
+
formData.comment,
|
|
6507
6507
|
totalAmount,
|
|
6508
6508
|
discountCode,
|
|
6509
6509
|
giftCards,
|
|
6510
6510
|
config,
|
|
6511
6511
|
upsellSelections,
|
|
6512
6512
|
]);
|
|
6513
|
-
const
|
|
6514
|
-
const
|
|
6515
|
-
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
6516
|
-
: 0;
|
|
6517
|
-
const discountAmount = discountCode?.discountAmount || 0;
|
|
6518
|
-
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
6519
|
-
const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
6513
|
+
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6514
|
+
const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
6520
6515
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
6521
6516
|
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6522
6517
|
}
|
|
@@ -11026,13 +11021,6 @@ function mergeStyles(...styles) {
|
|
|
11026
11021
|
return Object.assign({}, ...styles.filter(Boolean));
|
|
11027
11022
|
}
|
|
11028
11023
|
|
|
11029
|
-
function getEffectivePrice(upsell, round) {
|
|
11030
|
-
const dp = upsell.discountPercent ?? 0;
|
|
11031
|
-
if (dp <= 0)
|
|
11032
|
-
return upsell.price;
|
|
11033
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
11034
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
11035
|
-
}
|
|
11036
11024
|
// Local style aliases from shared styles
|
|
11037
11025
|
const cardStyles = cardStyles$1.base;
|
|
11038
11026
|
const sectionHeaderStyles = sectionStyles.header;
|
|
@@ -11044,6 +11032,11 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11044
11032
|
const { locale } = useLocale();
|
|
11045
11033
|
const timezone = useTimezone();
|
|
11046
11034
|
const roundEnabled = systemConfig?.roundPricesEnabled !== false;
|
|
11035
|
+
const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
|
|
11036
|
+
const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
|
|
11037
|
+
const raw = Math.round((baseAmount * basisPoints) / 10000);
|
|
11038
|
+
return round ? roundDiscountUp(raw) : raw;
|
|
11039
|
+
};
|
|
11047
11040
|
const [appliedVouchers, setAppliedVouchers] = React.useState([]);
|
|
11048
11041
|
const paymentSectionRef = React.useRef(null);
|
|
11049
11042
|
// Payment option: "deposit" or "full" - only relevant when deposit is available
|
|
@@ -11065,6 +11058,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11065
11058
|
const watchedParticipants = form.watch("participants");
|
|
11066
11059
|
const watchedCustomerName = form.watch("customerName");
|
|
11067
11060
|
const watchedCustomerEmail = form.watch("customerEmail");
|
|
11061
|
+
const watchedComment = form.watch("comment");
|
|
11062
|
+
const watchedCustomerPhone = form.watch("customerPhone");
|
|
11068
11063
|
const customerNameError = form.formState.errors.customerName;
|
|
11069
11064
|
const customerEmailError = form.formState.errors.customerEmail;
|
|
11070
11065
|
const watchedAcceptTerms = form.watch("acceptTerms");
|
|
@@ -11114,7 +11109,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11114
11109
|
participantUpsellIds.forEach(upsellId => {
|
|
11115
11110
|
const upsell = upsells.find(u => u.id === upsellId);
|
|
11116
11111
|
if (upsell) {
|
|
11117
|
-
total +=
|
|
11112
|
+
total += upsell.price;
|
|
11118
11113
|
}
|
|
11119
11114
|
});
|
|
11120
11115
|
}
|
|
@@ -11176,6 +11171,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11176
11171
|
participantIndices,
|
|
11177
11172
|
}));
|
|
11178
11173
|
}, [participantUpsells, watchedParticipants]);
|
|
11174
|
+
const paymentFormData = React.useMemo(() => ({
|
|
11175
|
+
customerName: watchedCustomerName,
|
|
11176
|
+
customerEmail: watchedCustomerEmail,
|
|
11177
|
+
customerPhone: watchedCustomerPhone,
|
|
11178
|
+
participants: watchedParticipants,
|
|
11179
|
+
comment: watchedComment,
|
|
11180
|
+
}), [watchedCustomerName, watchedCustomerEmail, watchedCustomerPhone, watchedParticipants, watchedComment]);
|
|
11179
11181
|
const appliedDiscountCode = appliedVouchers.find((v) => v.type === "discount");
|
|
11180
11182
|
const appliedGiftCards = appliedVouchers.filter((v) => v.type === "giftCard");
|
|
11181
11183
|
const handleVoucherValidated = React.useCallback((voucher, _error) => {
|
|
@@ -11212,7 +11214,10 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11212
11214
|
// TODO: discounts currently apply to base total only; extend when discount-applies-to-upsells flag is added
|
|
11213
11215
|
let newDiscountAmount = 0;
|
|
11214
11216
|
if (voucher.discountType === "percentage") {
|
|
11215
|
-
newDiscountAmount =
|
|
11217
|
+
newDiscountAmount = calcPercentDiscountAmount(newBaseTotal, voucher.discountValue || 0, roundEnabled);
|
|
11218
|
+
if (voucher.restrictions?.maxDiscount) {
|
|
11219
|
+
newDiscountAmount = Math.min(newDiscountAmount, voucher.restrictions.maxDiscount);
|
|
11220
|
+
}
|
|
11216
11221
|
}
|
|
11217
11222
|
else {
|
|
11218
11223
|
newDiscountAmount = voucher.discountValue || 0;
|
|
@@ -11390,7 +11395,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11390
11395
|
padding: 0,
|
|
11391
11396
|
}, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
|
|
11392
11397
|
const isSelected = (participantUpsells[index] || []).includes(upsell.id);
|
|
11393
|
-
return (jsxRuntime.jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsxRuntime.jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxRuntime.jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(
|
|
11398
|
+
return (jsxRuntime.jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsxRuntime.jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxRuntime.jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
|
|
11394
11399
|
}) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsxRuntime.jsx("div", { style: {
|
|
11395
11400
|
display: "flex",
|
|
11396
11401
|
flexDirection: "column",
|
|
@@ -11422,7 +11427,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11422
11427
|
const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
|
|
11423
11428
|
if (countWithUpsell === 0)
|
|
11424
11429
|
return null;
|
|
11425
|
-
const upsellLineTotal =
|
|
11430
|
+
const upsellLineTotal = upsell.price * countWithUpsell;
|
|
11426
11431
|
return (jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "13px" }, children: [jsxRuntime.jsxs("span", { style: { color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+ ", upsell.name, " (", countWithUpsell, "\u00D7)"] }), jsxRuntime.jsx("span", { style: { color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: formatCurrency(upsellLineTotal) })] }, upsell.id));
|
|
11427
11432
|
})] })), appliedVouchers.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("summary.subtotal") }), jsxRuntime.jsx("span", { style: {
|
|
11428
11433
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -11565,9 +11570,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11565
11570
|
}
|
|
11566
11571
|
: null;
|
|
11567
11572
|
if (systemConfig?.paymentProvider === "mollie") {
|
|
11568
|
-
return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData:
|
|
11573
|
+
return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] }));
|
|
11569
11574
|
}
|
|
11570
|
-
return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(
|
|
11575
|
+
return (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripePromise: stripePromise, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] }));
|
|
11571
11576
|
})() })] })] })] }) }));
|
|
11572
11577
|
}
|
|
11573
11578
|
|
|
@@ -11701,7 +11706,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11701
11706
|
if (targetPaymentIntentId) {
|
|
11702
11707
|
setIsLoading(true);
|
|
11703
11708
|
try {
|
|
11704
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment
|
|
11709
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
|
|
11705
11710
|
method: "POST",
|
|
11706
11711
|
headers: createApiHeaders(config),
|
|
11707
11712
|
body: JSON.stringify(createRequestBody(config, {
|
|
@@ -11717,7 +11722,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11717
11722
|
orderItems: data.orderItems,
|
|
11718
11723
|
purchases: data.purchases || [],
|
|
11719
11724
|
discount: data.discount || null,
|
|
11720
|
-
|
|
11725
|
+
providerPaymentDetails: data.stripePaymentIntent,
|
|
11721
11726
|
});
|
|
11722
11727
|
setEventDetails({
|
|
11723
11728
|
id: data.booking.eventInstance.id,
|
|
@@ -11725,7 +11730,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11725
11730
|
description: data.booking.eventInstance.eventType.description,
|
|
11726
11731
|
startTime: data.booking.eventInstance.startTime,
|
|
11727
11732
|
endTime: data.booking.eventInstance.endTime,
|
|
11728
|
-
price: data.booking.eventInstance.price || 0,
|
|
11733
|
+
price: data.booking.eventInstance.price || 0,
|
|
11729
11734
|
});
|
|
11730
11735
|
setFormData({
|
|
11731
11736
|
customerEmail: data.booking.customerEmail,
|
|
@@ -11733,9 +11738,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11733
11738
|
customerPhone: data.booking.customerPhone,
|
|
11734
11739
|
participants: data.booking.participants || [],
|
|
11735
11740
|
});
|
|
11736
|
-
// Set payment status from Stripe data or order status
|
|
11737
11741
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
11738
|
-
// Trigger Google Ads conversion tracking if payment is successful
|
|
11739
11742
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
11740
11743
|
if (finalPaymentStatus === "succeeded" &&
|
|
11741
11744
|
config.googleAds?.tagId &&
|
|
@@ -13685,11 +13688,10 @@ function formatUnavailableReason(reason, t) {
|
|
|
13685
13688
|
return t("upsells.reason.notEnoughSpots", { eventTypeName: reason.eventTypeName });
|
|
13686
13689
|
}
|
|
13687
13690
|
}
|
|
13688
|
-
function UpsellCard({ upsell, isSelected, participantCount, onSelect,
|
|
13691
|
+
function UpsellCard({ upsell, isSelected, participantCount, onSelect, }) {
|
|
13689
13692
|
const t = useTranslations();
|
|
13690
13693
|
const { locale } = useLocale();
|
|
13691
|
-
const
|
|
13692
|
-
const totalPrice = effectivePrice * participantCount;
|
|
13694
|
+
const totalPrice = upsell.price * participantCount;
|
|
13693
13695
|
const isDisabled = !upsell.available;
|
|
13694
13696
|
const getCardStyles = () => {
|
|
13695
13697
|
if (isDisabled)
|
|
@@ -13707,19 +13709,12 @@ function UpsellCard({ upsell, isSelected, participantCount, onSelect, roundPrice
|
|
|
13707
13709
|
weekday: "short",
|
|
13708
13710
|
day: "numeric",
|
|
13709
13711
|
month: "short",
|
|
13710
|
-
})] }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)" }, children: t("upsells.spotsFree", { count: upsell.suggestedEventInstance.availableSpots }) })] }))] }), jsxRuntime.jsxs("div", { style: priceContainerStyles, children: [jsxRuntime.jsxs("span", { style: pricePerPersonStyles, children: [formatCurrency(
|
|
13712
|
+
})] }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)" }, children: t("upsells.spotsFree", { count: upsell.suggestedEventInstance.availableSpots }) })] }))] }), jsxRuntime.jsxs("div", { style: priceContainerStyles, children: [jsxRuntime.jsxs("span", { style: pricePerPersonStyles, children: [formatCurrency(upsell.price), "/", t("common.perPerson")] }), participantCount > 1 && (jsxRuntime.jsxs("span", { style: priceTotalStyles, children: ["= ", formatCurrency(totalPrice)] }))] }), isDisabled && (jsxRuntime.jsx("div", { style: unavailableOverlayStyles, children: jsxRuntime.jsx("span", { children: upsell.unavailableReason
|
|
13711
13713
|
? formatUnavailableReason(upsell.unavailableReason, t)
|
|
13712
13714
|
: t("upsells.notAvailable") }) }))] }));
|
|
13713
13715
|
}
|
|
13714
13716
|
|
|
13715
|
-
function
|
|
13716
|
-
const dp = upsell.discountPercent ?? 0;
|
|
13717
|
-
if (dp <= 0)
|
|
13718
|
-
return upsell.price;
|
|
13719
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
13720
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
13721
|
-
}
|
|
13722
|
-
function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, isOpen, onClose, onSelect, onContinue, onBack, roundPricesEnabled = true, }) {
|
|
13717
|
+
function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, isOpen, onClose, onSelect, onContinue, onBack, }) {
|
|
13723
13718
|
const t = useTranslations();
|
|
13724
13719
|
const selectUpsell = (upsellId) => {
|
|
13725
13720
|
const exists = selectedUpsells.find((s) => s.upsellPackageId === upsellId);
|
|
@@ -13738,7 +13733,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13738
13733
|
return selectedUpsells.reduce((total, selection) => {
|
|
13739
13734
|
const upsell = upsells.find((u) => u.id === selection.upsellPackageId);
|
|
13740
13735
|
if (upsell) {
|
|
13741
|
-
return total +
|
|
13736
|
+
return total + upsell.price * selection.quantity;
|
|
13742
13737
|
}
|
|
13743
13738
|
return total;
|
|
13744
13739
|
}, 0);
|
|
@@ -13746,7 +13741,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13746
13741
|
const selectedTotal = calculateTotal();
|
|
13747
13742
|
const selectedCount = selectedUpsells.length;
|
|
13748
13743
|
const footerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), children: t("common.back") }), jsxRuntime.jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
|
|
13749
|
-
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsxRuntime.jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsxRuntime.jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsxRuntime.jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id)
|
|
13744
|
+
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsxRuntime.jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsxRuntime.jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsxRuntime.jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: "16px", paddingBottom: "16px", paddingTop: "16px", borderTop: "1px solid var(--bw-border-color)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
|
|
13750
13745
|
}
|
|
13751
13746
|
|
|
13752
13747
|
// Main widget component
|
|
@@ -13792,7 +13787,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13792
13787
|
const [isLoadingShowAll, setIsLoadingShowAll] = React.useState(false);
|
|
13793
13788
|
const [error, setError] = React.useState(null);
|
|
13794
13789
|
const [isSuccess, setIsSuccess] = React.useState(false);
|
|
13795
|
-
const [
|
|
13790
|
+
const [successPaymentId, setSuccessPaymentId] = React.useState(null);
|
|
13796
13791
|
const [systemConfig, setSystemConfig] = React.useState(null);
|
|
13797
13792
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
13798
13793
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
|
|
@@ -13858,7 +13853,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13858
13853
|
const stripeReturn = detectStripeReturn();
|
|
13859
13854
|
if (stripeReturn) {
|
|
13860
13855
|
if (stripeReturn.redirectStatus === "succeeded") {
|
|
13861
|
-
|
|
13856
|
+
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
13862
13857
|
setIsSuccess(true);
|
|
13863
13858
|
}
|
|
13864
13859
|
else if (stripeReturn.redirectStatus === "failed") {
|
|
@@ -14213,7 +14208,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14213
14208
|
};
|
|
14214
14209
|
const handleBookingSuccess = (result) => {
|
|
14215
14210
|
setIsSuccess(true);
|
|
14216
|
-
|
|
14211
|
+
setSuccessPaymentId(result.paymentIntent.id);
|
|
14217
14212
|
setSidebarOpen(false);
|
|
14218
14213
|
setShouldRenderBookingForm(false);
|
|
14219
14214
|
config.onSuccess?.(result);
|
|
@@ -14411,7 +14406,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14411
14406
|
setIsSuccess(false);
|
|
14412
14407
|
setCurrentStep("eventTypes");
|
|
14413
14408
|
setShowingPreview(true);
|
|
14414
|
-
|
|
14409
|
+
setSuccessPaymentId(null);
|
|
14415
14410
|
setShouldRenderInstanceSelection(false);
|
|
14416
14411
|
setShouldRenderUpsells(false);
|
|
14417
14412
|
setShouldRenderBookingForm(false);
|
|
@@ -14422,7 +14417,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14422
14417
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14423
14418
|
url.searchParams.delete("redirect_status");
|
|
14424
14419
|
window.history.replaceState({}, "", url.toString());
|
|
14425
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14420
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14426
14421
|
}
|
|
14427
14422
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
14428
14423
|
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -14435,7 +14430,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14435
14430
|
setIsSuccess(false);
|
|
14436
14431
|
setCurrentStep("eventTypes");
|
|
14437
14432
|
setShowingPreview(true);
|
|
14438
|
-
|
|
14433
|
+
setSuccessPaymentId(null);
|
|
14439
14434
|
setShouldRenderInstanceSelection(false);
|
|
14440
14435
|
setShouldRenderBookingForm(false);
|
|
14441
14436
|
const url = new URL(window.location.href);
|
|
@@ -14443,7 +14438,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14443
14438
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14444
14439
|
url.searchParams.delete("redirect_status");
|
|
14445
14440
|
window.history.replaceState({}, "", url.toString());
|
|
14446
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14441
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14447
14442
|
}
|
|
14448
14443
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
14449
14444
|
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -14472,11 +14467,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14472
14467
|
setShouldRenderInstanceSelection(true);
|
|
14473
14468
|
}
|
|
14474
14469
|
}, children: config.buttonText ||
|
|
14475
|
-
(isDirectInstanceMode ? t("button.bookNow") : t("button.selectDate")) }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack
|
|
14470
|
+
(isDirectInstanceMode ? t("button.bookNow") : t("button.selectDate")) }), shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), 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, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => setCurrentStep(isDirectInstanceMode ? "eventTypes" : "eventInstances"), onBackToEventTypes: () => setSidebarOpen(false), selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => setCurrentStep(isDirectInstanceMode ? "eventTypes" : "eventInstances"), systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
14476
14471
|
setIsSuccess(false);
|
|
14477
14472
|
setCurrentStep("eventTypes");
|
|
14478
14473
|
setSidebarOpen(false);
|
|
14479
|
-
|
|
14474
|
+
setSuccessPaymentId(null);
|
|
14480
14475
|
setShouldRenderInstanceSelection(false);
|
|
14481
14476
|
setShouldRenderUpsells(false);
|
|
14482
14477
|
setShouldRenderBookingForm(false);
|
|
@@ -14487,7 +14482,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14487
14482
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14488
14483
|
url.searchParams.delete("redirect_status");
|
|
14489
14484
|
window.history.replaceState({}, "", url.toString());
|
|
14490
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14485
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14491
14486
|
}
|
|
14492
14487
|
// Cards mode (default) - show event type selection
|
|
14493
14488
|
const cardsView = (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount() }));
|
|
@@ -14519,10 +14514,10 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14519
14514
|
};
|
|
14520
14515
|
};
|
|
14521
14516
|
const backHandlers = getBackHandlers();
|
|
14522
|
-
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 })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack
|
|
14517
|
+
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 })), 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, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: backHandlers.onBackToEventInstances, onBackToEventTypes: backHandlers.onBackToEventTypes, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
14523
14518
|
setIsSuccess(false);
|
|
14524
14519
|
setCurrentStep("eventTypes");
|
|
14525
|
-
|
|
14520
|
+
setSuccessPaymentId(null);
|
|
14526
14521
|
setShouldRenderInstanceSelection(false);
|
|
14527
14522
|
setShouldRenderUpsells(false);
|
|
14528
14523
|
setShouldRenderBookingForm(false);
|
|
@@ -14533,7 +14528,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14533
14528
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14534
14529
|
url.searchParams.delete("redirect_status");
|
|
14535
14530
|
window.history.replaceState({}, "", url.toString());
|
|
14536
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14531
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14537
14532
|
}
|
|
14538
14533
|
function UniversalBookingWidget(props) {
|
|
14539
14534
|
const [languagePolicy, setLanguagePolicy] = React.useState(null);
|