@bigz-app/booking-widget 1.1.7 → 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 +165 -180
- 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 +165 -180
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +165 -180
- 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,
|
|
@@ -6511,13 +6510,8 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6511
6510
|
config,
|
|
6512
6511
|
upsellSelections,
|
|
6513
6512
|
]);
|
|
6514
|
-
const
|
|
6515
|
-
const
|
|
6516
|
-
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
6517
|
-
: 0;
|
|
6518
|
-
const discountAmount = discountCode?.discountAmount || 0;
|
|
6519
|
-
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
6520
|
-
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);
|
|
6521
6515
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
6522
6516
|
return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6523
6517
|
}
|
|
@@ -11027,13 +11021,6 @@ function mergeStyles(...styles) {
|
|
|
11027
11021
|
return Object.assign({}, ...styles.filter(Boolean));
|
|
11028
11022
|
}
|
|
11029
11023
|
|
|
11030
|
-
function getEffectivePrice(upsell, round) {
|
|
11031
|
-
const dp = upsell.discountPercent ?? 0;
|
|
11032
|
-
if (dp <= 0)
|
|
11033
|
-
return upsell.price;
|
|
11034
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
11035
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
11036
|
-
}
|
|
11037
11024
|
// Local style aliases from shared styles
|
|
11038
11025
|
const cardStyles = cardStyles$1.base;
|
|
11039
11026
|
const sectionHeaderStyles = sectionStyles.header;
|
|
@@ -11045,6 +11032,11 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11045
11032
|
const { locale } = useLocale();
|
|
11046
11033
|
const timezone = useTimezone();
|
|
11047
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
|
+
};
|
|
11048
11040
|
const [appliedVouchers, setAppliedVouchers] = React.useState([]);
|
|
11049
11041
|
const paymentSectionRef = React.useRef(null);
|
|
11050
11042
|
// Payment option: "deposit" or "full" - only relevant when deposit is available
|
|
@@ -11117,7 +11109,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11117
11109
|
participantUpsellIds.forEach(upsellId => {
|
|
11118
11110
|
const upsell = upsells.find(u => u.id === upsellId);
|
|
11119
11111
|
if (upsell) {
|
|
11120
|
-
total +=
|
|
11112
|
+
total += upsell.price;
|
|
11121
11113
|
}
|
|
11122
11114
|
});
|
|
11123
11115
|
}
|
|
@@ -11222,7 +11214,10 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11222
11214
|
// TODO: discounts currently apply to base total only; extend when discount-applies-to-upsells flag is added
|
|
11223
11215
|
let newDiscountAmount = 0;
|
|
11224
11216
|
if (voucher.discountType === "percentage") {
|
|
11225
|
-
newDiscountAmount =
|
|
11217
|
+
newDiscountAmount = calcPercentDiscountAmount(newBaseTotal, voucher.discountValue || 0, roundEnabled);
|
|
11218
|
+
if (voucher.restrictions?.maxDiscount) {
|
|
11219
|
+
newDiscountAmount = Math.min(newDiscountAmount, voucher.restrictions.maxDiscount);
|
|
11220
|
+
}
|
|
11226
11221
|
}
|
|
11227
11222
|
else {
|
|
11228
11223
|
newDiscountAmount = voucher.discountValue || 0;
|
|
@@ -11400,7 +11395,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11400
11395
|
padding: 0,
|
|
11401
11396
|
}, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
|
|
11402
11397
|
const isSelected = (participantUpsells[index] || []).includes(upsell.id);
|
|
11403
|
-
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));
|
|
11404
11399
|
}) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsxRuntime.jsx("div", { style: {
|
|
11405
11400
|
display: "flex",
|
|
11406
11401
|
flexDirection: "column",
|
|
@@ -11432,7 +11427,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11432
11427
|
const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
|
|
11433
11428
|
if (countWithUpsell === 0)
|
|
11434
11429
|
return null;
|
|
11435
|
-
const upsellLineTotal =
|
|
11430
|
+
const upsellLineTotal = upsell.price * countWithUpsell;
|
|
11436
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));
|
|
11437
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: {
|
|
11438
11433
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -11577,7 +11572,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11577
11572
|
if (systemConfig?.paymentProvider === "mollie") {
|
|
11578
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 })] }));
|
|
11579
11574
|
}
|
|
11580
|
-
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() })] }));
|
|
11581
11576
|
})() })] })] })] }) }));
|
|
11582
11577
|
}
|
|
11583
11578
|
|
|
@@ -11711,7 +11706,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11711
11706
|
if (targetPaymentIntentId) {
|
|
11712
11707
|
setIsLoading(true);
|
|
11713
11708
|
try {
|
|
11714
|
-
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"), {
|
|
11715
11710
|
method: "POST",
|
|
11716
11711
|
headers: createApiHeaders(config),
|
|
11717
11712
|
body: JSON.stringify(createRequestBody(config, {
|
|
@@ -11727,7 +11722,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11727
11722
|
orderItems: data.orderItems,
|
|
11728
11723
|
purchases: data.purchases || [],
|
|
11729
11724
|
discount: data.discount || null,
|
|
11730
|
-
|
|
11725
|
+
providerPaymentDetails: data.stripePaymentIntent,
|
|
11731
11726
|
});
|
|
11732
11727
|
setEventDetails({
|
|
11733
11728
|
id: data.booking.eventInstance.id,
|
|
@@ -11735,7 +11730,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11735
11730
|
description: data.booking.eventInstance.eventType.description,
|
|
11736
11731
|
startTime: data.booking.eventInstance.startTime,
|
|
11737
11732
|
endTime: data.booking.eventInstance.endTime,
|
|
11738
|
-
price: data.booking.eventInstance.price || 0,
|
|
11733
|
+
price: data.booking.eventInstance.price || 0,
|
|
11739
11734
|
});
|
|
11740
11735
|
setFormData({
|
|
11741
11736
|
customerEmail: data.booking.customerEmail,
|
|
@@ -11743,9 +11738,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11743
11738
|
customerPhone: data.booking.customerPhone,
|
|
11744
11739
|
participants: data.booking.participants || [],
|
|
11745
11740
|
});
|
|
11746
|
-
// Set payment status from Stripe data or order status
|
|
11747
11741
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
11748
|
-
// Trigger Google Ads conversion tracking if payment is successful
|
|
11749
11742
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
11750
11743
|
if (finalPaymentStatus === "succeeded" &&
|
|
11751
11744
|
config.googleAds?.tagId &&
|
|
@@ -13695,11 +13688,10 @@ function formatUnavailableReason(reason, t) {
|
|
|
13695
13688
|
return t("upsells.reason.notEnoughSpots", { eventTypeName: reason.eventTypeName });
|
|
13696
13689
|
}
|
|
13697
13690
|
}
|
|
13698
|
-
function UpsellCard({ upsell, isSelected, participantCount, onSelect,
|
|
13691
|
+
function UpsellCard({ upsell, isSelected, participantCount, onSelect, }) {
|
|
13699
13692
|
const t = useTranslations();
|
|
13700
13693
|
const { locale } = useLocale();
|
|
13701
|
-
const
|
|
13702
|
-
const totalPrice = effectivePrice * participantCount;
|
|
13694
|
+
const totalPrice = upsell.price * participantCount;
|
|
13703
13695
|
const isDisabled = !upsell.available;
|
|
13704
13696
|
const getCardStyles = () => {
|
|
13705
13697
|
if (isDisabled)
|
|
@@ -13717,19 +13709,12 @@ function UpsellCard({ upsell, isSelected, participantCount, onSelect, roundPrice
|
|
|
13717
13709
|
weekday: "short",
|
|
13718
13710
|
day: "numeric",
|
|
13719
13711
|
month: "short",
|
|
13720
|
-
})] }), 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
|
|
13721
13713
|
? formatUnavailableReason(upsell.unavailableReason, t)
|
|
13722
13714
|
: t("upsells.notAvailable") }) }))] }));
|
|
13723
13715
|
}
|
|
13724
13716
|
|
|
13725
|
-
function
|
|
13726
|
-
const dp = upsell.discountPercent ?? 0;
|
|
13727
|
-
if (dp <= 0)
|
|
13728
|
-
return upsell.price;
|
|
13729
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
13730
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
13731
|
-
}
|
|
13732
|
-
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, }) {
|
|
13733
13718
|
const t = useTranslations();
|
|
13734
13719
|
const selectUpsell = (upsellId) => {
|
|
13735
13720
|
const exists = selectedUpsells.find((s) => s.upsellPackageId === upsellId);
|
|
@@ -13748,7 +13733,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13748
13733
|
return selectedUpsells.reduce((total, selection) => {
|
|
13749
13734
|
const upsell = upsells.find((u) => u.id === selection.upsellPackageId);
|
|
13750
13735
|
if (upsell) {
|
|
13751
|
-
return total +
|
|
13736
|
+
return total + upsell.price * selection.quantity;
|
|
13752
13737
|
}
|
|
13753
13738
|
return total;
|
|
13754
13739
|
}, 0);
|
|
@@ -13756,7 +13741,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13756
13741
|
const selectedTotal = calculateTotal();
|
|
13757
13742
|
const selectedCount = selectedUpsells.length;
|
|
13758
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") })] }));
|
|
13759
|
-
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)] })] }))] }) }));
|
|
13760
13745
|
}
|
|
13761
13746
|
|
|
13762
13747
|
// Main widget component
|
|
@@ -13802,7 +13787,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13802
13787
|
const [isLoadingShowAll, setIsLoadingShowAll] = React.useState(false);
|
|
13803
13788
|
const [error, setError] = React.useState(null);
|
|
13804
13789
|
const [isSuccess, setIsSuccess] = React.useState(false);
|
|
13805
|
-
const [
|
|
13790
|
+
const [successPaymentId, setSuccessPaymentId] = React.useState(null);
|
|
13806
13791
|
const [systemConfig, setSystemConfig] = React.useState(null);
|
|
13807
13792
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
13808
13793
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
|
|
@@ -13868,7 +13853,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13868
13853
|
const stripeReturn = detectStripeReturn();
|
|
13869
13854
|
if (stripeReturn) {
|
|
13870
13855
|
if (stripeReturn.redirectStatus === "succeeded") {
|
|
13871
|
-
|
|
13856
|
+
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
13872
13857
|
setIsSuccess(true);
|
|
13873
13858
|
}
|
|
13874
13859
|
else if (stripeReturn.redirectStatus === "failed") {
|
|
@@ -14223,7 +14208,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14223
14208
|
};
|
|
14224
14209
|
const handleBookingSuccess = (result) => {
|
|
14225
14210
|
setIsSuccess(true);
|
|
14226
|
-
|
|
14211
|
+
setSuccessPaymentId(result.paymentIntent.id);
|
|
14227
14212
|
setSidebarOpen(false);
|
|
14228
14213
|
setShouldRenderBookingForm(false);
|
|
14229
14214
|
config.onSuccess?.(result);
|
|
@@ -14421,7 +14406,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14421
14406
|
setIsSuccess(false);
|
|
14422
14407
|
setCurrentStep("eventTypes");
|
|
14423
14408
|
setShowingPreview(true);
|
|
14424
|
-
|
|
14409
|
+
setSuccessPaymentId(null);
|
|
14425
14410
|
setShouldRenderInstanceSelection(false);
|
|
14426
14411
|
setShouldRenderUpsells(false);
|
|
14427
14412
|
setShouldRenderBookingForm(false);
|
|
@@ -14432,7 +14417,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14432
14417
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14433
14418
|
url.searchParams.delete("redirect_status");
|
|
14434
14419
|
window.history.replaceState({}, "", url.toString());
|
|
14435
|
-
}, 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 }))] }));
|
|
14436
14421
|
}
|
|
14437
14422
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
14438
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: () => {
|
|
@@ -14445,7 +14430,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14445
14430
|
setIsSuccess(false);
|
|
14446
14431
|
setCurrentStep("eventTypes");
|
|
14447
14432
|
setShowingPreview(true);
|
|
14448
|
-
|
|
14433
|
+
setSuccessPaymentId(null);
|
|
14449
14434
|
setShouldRenderInstanceSelection(false);
|
|
14450
14435
|
setShouldRenderBookingForm(false);
|
|
14451
14436
|
const url = new URL(window.location.href);
|
|
@@ -14453,7 +14438,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14453
14438
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14454
14439
|
url.searchParams.delete("redirect_status");
|
|
14455
14440
|
window.history.replaceState({}, "", url.toString());
|
|
14456
|
-
}, 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 }))] }));
|
|
14457
14442
|
}
|
|
14458
14443
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
14459
14444
|
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -14482,11 +14467,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14482
14467
|
setShouldRenderInstanceSelection(true);
|
|
14483
14468
|
}
|
|
14484
14469
|
}, children: config.buttonText ||
|
|
14485
|
-
(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: () => {
|
|
14486
14471
|
setIsSuccess(false);
|
|
14487
14472
|
setCurrentStep("eventTypes");
|
|
14488
14473
|
setSidebarOpen(false);
|
|
14489
|
-
|
|
14474
|
+
setSuccessPaymentId(null);
|
|
14490
14475
|
setShouldRenderInstanceSelection(false);
|
|
14491
14476
|
setShouldRenderUpsells(false);
|
|
14492
14477
|
setShouldRenderBookingForm(false);
|
|
@@ -14497,7 +14482,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14497
14482
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14498
14483
|
url.searchParams.delete("redirect_status");
|
|
14499
14484
|
window.history.replaceState({}, "", url.toString());
|
|
14500
|
-
}, 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 }))] }));
|
|
14501
14486
|
}
|
|
14502
14487
|
// Cards mode (default) - show event type selection
|
|
14503
14488
|
const cardsView = (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount() }));
|
|
@@ -14529,10 +14514,10 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14529
14514
|
};
|
|
14530
14515
|
};
|
|
14531
14516
|
const backHandlers = getBackHandlers();
|
|
14532
|
-
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: () => {
|
|
14533
14518
|
setIsSuccess(false);
|
|
14534
14519
|
setCurrentStep("eventTypes");
|
|
14535
|
-
|
|
14520
|
+
setSuccessPaymentId(null);
|
|
14536
14521
|
setShouldRenderInstanceSelection(false);
|
|
14537
14522
|
setShouldRenderUpsells(false);
|
|
14538
14523
|
setShouldRenderBookingForm(false);
|
|
@@ -14543,7 +14528,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14543
14528
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14544
14529
|
url.searchParams.delete("redirect_status");
|
|
14545
14530
|
window.history.replaceState({}, "", url.toString());
|
|
14546
|
-
}, 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 }))] }));
|
|
14547
14532
|
}
|
|
14548
14533
|
function UniversalBookingWidget(props) {
|
|
14549
14534
|
const [languagePolicy, setLanguagePolicy] = React.useState(null);
|