@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.esm.js
CHANGED
|
@@ -203,7 +203,7 @@ const de$1 = {
|
|
|
203
203
|
"error.loadBookingData": "Fehler beim Abrufen der Buchungsdaten",
|
|
204
204
|
"error.processingError": "Ein Fehler ist bei der Verarbeitung aufgetreten.",
|
|
205
205
|
"error.createBooking": "Fehler beim Erstellen der Buchung",
|
|
206
|
-
"error.
|
|
206
|
+
"error.createPayment": "Fehler beim Erstellen der Zahlung",
|
|
207
207
|
"error.paymentProcessing": "Fehler beim Verarbeiten der Zahlung",
|
|
208
208
|
"error.paymentFailed": "Die Zahlung war nicht erfolgreich. Bitte versuche es erneut.",
|
|
209
209
|
"error.paymentIncomplete": "Die Zahlung konnte nicht abgeschlossen werden. Bitte versuche es erneut.",
|
|
@@ -317,7 +317,7 @@ const de$1 = {
|
|
|
317
317
|
"voucher.giftCardApplied": "−{{amount}} Gutschein",
|
|
318
318
|
"voucher.remaining": "Rest: {{amount}}",
|
|
319
319
|
"voucher.remove": "Entfernen",
|
|
320
|
-
"voucher.alreadyHasDiscount": "
|
|
320
|
+
"voucher.alreadyHasDiscount": "Du kannst weitere Gutscheine hinzufügen.",
|
|
321
321
|
// Booking success
|
|
322
322
|
"success.title": "Reservierung erfolgreich!",
|
|
323
323
|
"success.bookingDetails": "Buchungsdetails",
|
|
@@ -412,7 +412,7 @@ const en = {
|
|
|
412
412
|
"error.loadBookingData": "Error fetching booking data",
|
|
413
413
|
"error.processingError": "An error occurred during processing.",
|
|
414
414
|
"error.createBooking": "Error creating booking",
|
|
415
|
-
"error.
|
|
415
|
+
"error.createPayment": "Error creating payment",
|
|
416
416
|
"error.paymentProcessing": "Error processing payment",
|
|
417
417
|
"error.paymentFailed": "The payment was not successful. Please try again.",
|
|
418
418
|
"error.paymentIncomplete": "The payment could not be completed. Please try again.",
|
|
@@ -621,7 +621,7 @@ const es = {
|
|
|
621
621
|
"error.loadBookingData": "Error al obtener datos de la reserva",
|
|
622
622
|
"error.processingError": "Ocurrió un error durante el procesamiento.",
|
|
623
623
|
"error.createBooking": "Error al crear la reserva",
|
|
624
|
-
"error.
|
|
624
|
+
"error.createPayment": "Error al crear el pago",
|
|
625
625
|
"error.paymentProcessing": "Error al procesar el pago",
|
|
626
626
|
"error.paymentFailed": "El pago no fue exitoso. Por favor, inténtalo de nuevo.",
|
|
627
627
|
"error.paymentIncomplete": "El pago no pudo completarse. Por favor, inténtalo de nuevo.",
|
|
@@ -830,7 +830,7 @@ const pt = {
|
|
|
830
830
|
"error.loadBookingData": "Erro ao obter dados da reserva",
|
|
831
831
|
"error.processingError": "Ocorreu um erro durante o processamento.",
|
|
832
832
|
"error.createBooking": "Erro ao criar reserva",
|
|
833
|
-
"error.
|
|
833
|
+
"error.createPayment": "Erro ao criar pagamento",
|
|
834
834
|
"error.paymentProcessing": "Erro ao processar pagamento",
|
|
835
835
|
"error.paymentFailed": "O pagamento não foi bem-sucedido. Por favor, tente novamente.",
|
|
836
836
|
"error.paymentIncomplete": "O pagamento não pôde ser concluído. Por favor, tente novamente.",
|
|
@@ -1039,7 +1039,7 @@ const sv = {
|
|
|
1039
1039
|
"error.loadBookingData": "Fel vid hämtning av bokningsdata",
|
|
1040
1040
|
"error.processingError": "Ett fel uppstod vid bearbetningen.",
|
|
1041
1041
|
"error.createBooking": "Fel vid skapande av bokning",
|
|
1042
|
-
"error.
|
|
1042
|
+
"error.createPayment": "Fel vid skapande av betalning",
|
|
1043
1043
|
"error.paymentProcessing": "Fel vid bearbetning av betalning",
|
|
1044
1044
|
"error.paymentFailed": "Betalningen lyckades inte. Försök igen.",
|
|
1045
1045
|
"error.paymentIncomplete": "Betalningen kunde inte slutföras. Försök igen.",
|
|
@@ -4398,6 +4398,108 @@ const IconChevronLeft = ({ size = 20, color = "white", className, }) => (jsx("sv
|
|
|
4398
4398
|
// Chevron Right icon - used for carousel navigation
|
|
4399
4399
|
const IconChevronRight = ({ size = 20, color = "white", className, }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: jsx("polyline", { points: "9 18 15 12 9 6" }) }));
|
|
4400
4400
|
|
|
4401
|
+
function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, upsellSelections = [], }) {
|
|
4402
|
+
const t = useTranslations();
|
|
4403
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
4404
|
+
const [error, setError] = useState(null);
|
|
4405
|
+
const handleBooking = async () => {
|
|
4406
|
+
setIsLoading(true);
|
|
4407
|
+
setError(null);
|
|
4408
|
+
try {
|
|
4409
|
+
const requestData = {
|
|
4410
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
4411
|
+
organizationId: config.organizationId,
|
|
4412
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
4413
|
+
discountCode: discountCode?.code,
|
|
4414
|
+
giftCardCodes: giftCards.map((gc) => gc.code),
|
|
4415
|
+
customerName: formData.customerName?.trim(),
|
|
4416
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
4417
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
4418
|
+
comment: formData.comment?.trim(),
|
|
4419
|
+
paymentMethod: "gift_card",
|
|
4420
|
+
...(upsellSelections.length > 0 && { upsellSelections }),
|
|
4421
|
+
};
|
|
4422
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
|
|
4423
|
+
method: "POST",
|
|
4424
|
+
headers: createApiHeaders(config),
|
|
4425
|
+
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
4426
|
+
});
|
|
4427
|
+
const data = await response.json();
|
|
4428
|
+
if (response.ok) {
|
|
4429
|
+
onSuccess({
|
|
4430
|
+
booking: data.booking,
|
|
4431
|
+
order: data.order,
|
|
4432
|
+
giftCardRedemptions: data.giftCardRedemptions,
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
else {
|
|
4436
|
+
setError(data.error || t("error.createBooking"));
|
|
4437
|
+
onError(data.error || t("error.createBooking"));
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
catch (err) {
|
|
4441
|
+
setError(err.message || t("error.createBooking"));
|
|
4442
|
+
onError(err.message || t("error.createBooking"));
|
|
4443
|
+
}
|
|
4444
|
+
finally {
|
|
4445
|
+
setIsLoading(false);
|
|
4446
|
+
}
|
|
4447
|
+
};
|
|
4448
|
+
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
4449
|
+
return (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [jsxs("div", { style: {
|
|
4450
|
+
backgroundColor: "rgba(var(--bw-success-color), 0.15)",
|
|
4451
|
+
border: "1px solid rgba(var(--bw-success-color), 0.4)",
|
|
4452
|
+
borderRadius: "var(--bw-border-radius)",
|
|
4453
|
+
padding: "16px",
|
|
4454
|
+
}, children: [jsxs("div", { style: {
|
|
4455
|
+
display: "flex",
|
|
4456
|
+
alignItems: "center",
|
|
4457
|
+
gap: "8px",
|
|
4458
|
+
marginBottom: "8px",
|
|
4459
|
+
color: "var(--bw-success-color)",
|
|
4460
|
+
fontFamily: "var(--bw-font-family)",
|
|
4461
|
+
fontWeight: 600,
|
|
4462
|
+
}, children: ["\uD83C\uDF81 ", t("payment.giftCardCovered")] }), jsx("div", { style: {
|
|
4463
|
+
fontSize: "16px",
|
|
4464
|
+
color: "var(--bw-text-muted)",
|
|
4465
|
+
fontFamily: "var(--bw-font-family)",
|
|
4466
|
+
}, children: t("payment.giftCardBalance", { amount: formatCurrency(totalGiftCardAmount) }) })] }), error && (jsxs("div", { style: {
|
|
4467
|
+
backgroundColor: "rgba(var(--bw-error-color), 0.15)",
|
|
4468
|
+
border: "1px solid rgba(var(--bw-error-color), 0.4)",
|
|
4469
|
+
borderRadius: "var(--bw-border-radius)",
|
|
4470
|
+
padding: "16px",
|
|
4471
|
+
color: "var(--bw-error-color)",
|
|
4472
|
+
fontSize: "16px",
|
|
4473
|
+
fontFamily: "var(--bw-font-family)",
|
|
4474
|
+
}, children: ["\u26A0\uFE0F ", error] })), jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
|
|
4475
|
+
width: "100%",
|
|
4476
|
+
padding: "12px 24px",
|
|
4477
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
4478
|
+
color: "#ffffff",
|
|
4479
|
+
border: "none",
|
|
4480
|
+
borderRadius: "var(--bw-border-radius)",
|
|
4481
|
+
fontSize: "16px",
|
|
4482
|
+
fontWeight: 600,
|
|
4483
|
+
fontFamily: "var(--bw-font-family)",
|
|
4484
|
+
transition: "all 0.2s ease",
|
|
4485
|
+
display: "flex",
|
|
4486
|
+
alignItems: "center",
|
|
4487
|
+
justifyContent: "center",
|
|
4488
|
+
gap: "8px",
|
|
4489
|
+
cursor: isLoading ? "not-allowed" : "pointer",
|
|
4490
|
+
opacity: isLoading ? 0.6 : 1,
|
|
4491
|
+
}, children: isLoading ? (jsxs(Fragment, { children: [spinner("#ffffff"), t("button.creatingBooking")] })) : (t("button.bookWithGiftCard")) })] }));
|
|
4492
|
+
}
|
|
4493
|
+
/**
|
|
4494
|
+
* Calculate whether gift cards fully cover the order after discounts.
|
|
4495
|
+
*/
|
|
4496
|
+
function isGiftCardFullyCovered(giftCards, eventPrice, participantCount, discountAmount) {
|
|
4497
|
+
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
4498
|
+
const baseTotal = eventPrice * participantCount;
|
|
4499
|
+
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
4500
|
+
return totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4401
4503
|
const mollieLocaleMap = {
|
|
4402
4504
|
de: "de_DE",
|
|
4403
4505
|
en: "en_US",
|
|
@@ -4458,13 +4560,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4458
4560
|
const mollieRef = useRef(null);
|
|
4459
4561
|
const cardFormRef = useRef(null);
|
|
4460
4562
|
const cardContainerRef = useRef(null);
|
|
4461
|
-
const
|
|
4462
|
-
const
|
|
4463
|
-
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
4464
|
-
: 0;
|
|
4465
|
-
const discountAmount = discountCode?.discountAmount || 0;
|
|
4466
|
-
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
4467
|
-
const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
4563
|
+
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
4564
|
+
const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
4468
4565
|
const isCreditCard = selectedMethod === "creditcard";
|
|
4469
4566
|
useEffect(() => {
|
|
4470
4567
|
if (isFullyCoveredByGiftCards)
|
|
@@ -4571,7 +4668,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4571
4668
|
};
|
|
4572
4669
|
}, [isCreditCard, mollieProfileId, mollieTestmode, isFullyCoveredByGiftCards]);
|
|
4573
4670
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
4574
|
-
return
|
|
4671
|
+
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
4575
4672
|
}
|
|
4576
4673
|
const validateBeforePayment = () => {
|
|
4577
4674
|
const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
|
|
@@ -4613,7 +4710,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4613
4710
|
}
|
|
4614
4711
|
const { token, error: tokenError } = await mollieRef.current.createToken();
|
|
4615
4712
|
if (tokenError || !token) {
|
|
4616
|
-
setPaymentError(tokenError?.message || t("error.
|
|
4713
|
+
setPaymentError(tokenError?.message || t("error.createPayment"));
|
|
4617
4714
|
return;
|
|
4618
4715
|
}
|
|
4619
4716
|
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
|
|
@@ -4629,8 +4726,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4629
4726
|
_onSuccess({ paymentIntent: { id: data.molliePaymentId } });
|
|
4630
4727
|
}
|
|
4631
4728
|
else {
|
|
4632
|
-
setPaymentError(data.error || t("error.
|
|
4633
|
-
onError(data.error || t("error.
|
|
4729
|
+
setPaymentError(data.error || t("error.createPayment"));
|
|
4730
|
+
onError(data.error || t("error.createPayment"));
|
|
4634
4731
|
}
|
|
4635
4732
|
}
|
|
4636
4733
|
catch (err) {
|
|
@@ -4662,8 +4759,8 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
|
|
|
4662
4759
|
window.location.href = data.checkoutUrl;
|
|
4663
4760
|
}
|
|
4664
4761
|
else {
|
|
4665
|
-
setPaymentError(data.error || t("error.
|
|
4666
|
-
onError(data.error || t("error.
|
|
4762
|
+
setPaymentError(data.error || t("error.createPayment"));
|
|
4763
|
+
onError(data.error || t("error.createPayment"));
|
|
4667
4764
|
}
|
|
4668
4765
|
}
|
|
4669
4766
|
catch (err) {
|
|
@@ -6133,106 +6230,6 @@ var reactStripe_umd = {exports: {}};
|
|
|
6133
6230
|
|
|
6134
6231
|
var reactStripe_umdExports = reactStripe_umd.exports;
|
|
6135
6232
|
|
|
6136
|
-
// Component for bookings fully covered by gift cards (no Stripe payment needed)
|
|
6137
|
-
function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, upsellSelections = [], }) {
|
|
6138
|
-
const t = useTranslations();
|
|
6139
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
6140
|
-
const [error, setError] = useState(null);
|
|
6141
|
-
const handleBooking = async () => {
|
|
6142
|
-
setIsLoading(true);
|
|
6143
|
-
setError(null);
|
|
6144
|
-
try {
|
|
6145
|
-
const requestData = {
|
|
6146
|
-
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
6147
|
-
organizationId: config.organizationId,
|
|
6148
|
-
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
6149
|
-
discountCode: discountCode?.code,
|
|
6150
|
-
giftCardCodes: giftCards.map((gc) => gc.code),
|
|
6151
|
-
customerName: formData.customerName?.trim(),
|
|
6152
|
-
customerEmail: formData.customerEmail?.trim(),
|
|
6153
|
-
customerPhone: formData.customerPhone?.trim(),
|
|
6154
|
-
comment: formData.comment?.trim(),
|
|
6155
|
-
paymentMethod: "gift_card",
|
|
6156
|
-
...(upsellSelections.length > 0 && { upsellSelections }),
|
|
6157
|
-
};
|
|
6158
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
|
|
6159
|
-
method: "POST",
|
|
6160
|
-
headers: createApiHeaders(config),
|
|
6161
|
-
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
6162
|
-
});
|
|
6163
|
-
const data = await response.json();
|
|
6164
|
-
if (response.ok) {
|
|
6165
|
-
onSuccess({
|
|
6166
|
-
booking: data.booking,
|
|
6167
|
-
order: data.order,
|
|
6168
|
-
giftCardRedemptions: data.giftCardRedemptions,
|
|
6169
|
-
});
|
|
6170
|
-
}
|
|
6171
|
-
else {
|
|
6172
|
-
setError(data.error || t("error.createBooking"));
|
|
6173
|
-
onError(data.error || t("error.createBooking"));
|
|
6174
|
-
}
|
|
6175
|
-
}
|
|
6176
|
-
catch (err) {
|
|
6177
|
-
setError(err.message || t("error.createBooking"));
|
|
6178
|
-
onError(err.message || t("error.createBooking"));
|
|
6179
|
-
}
|
|
6180
|
-
finally {
|
|
6181
|
-
setIsLoading(false);
|
|
6182
|
-
}
|
|
6183
|
-
};
|
|
6184
|
-
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
6185
|
-
return (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [jsxs("div", { style: {
|
|
6186
|
-
backgroundColor: "rgba(var(--bw-success-color), 0.15)",
|
|
6187
|
-
border: "1px solid rgba(var(--bw-success-color), 0.4)",
|
|
6188
|
-
borderRadius: "var(--bw-border-radius)",
|
|
6189
|
-
padding: "16px",
|
|
6190
|
-
}, children: [jsxs("div", { style: {
|
|
6191
|
-
display: "flex",
|
|
6192
|
-
alignItems: "center",
|
|
6193
|
-
gap: "8px",
|
|
6194
|
-
marginBottom: "8px",
|
|
6195
|
-
color: "var(--bw-success-color)",
|
|
6196
|
-
fontFamily: "var(--bw-font-family)",
|
|
6197
|
-
fontWeight: 600,
|
|
6198
|
-
}, children: ["\uD83C\uDF81 ", t("payment.giftCardCovered")] }), jsx("div", { style: {
|
|
6199
|
-
fontSize: "16px",
|
|
6200
|
-
color: "var(--bw-text-muted)",
|
|
6201
|
-
fontFamily: "var(--bw-font-family)",
|
|
6202
|
-
}, children: t("payment.giftCardBalance", { amount: formatCurrency(totalGiftCardAmount) }) })] }), error && (jsxs("div", { style: {
|
|
6203
|
-
backgroundColor: "rgba(var(--bw-error-color), 0.15)",
|
|
6204
|
-
border: "1px solid rgba(var(--bw-error-color), 0.4)",
|
|
6205
|
-
borderRadius: "var(--bw-border-radius)",
|
|
6206
|
-
padding: "16px",
|
|
6207
|
-
color: "var(--bw-error-color)",
|
|
6208
|
-
fontSize: "16px",
|
|
6209
|
-
fontFamily: "var(--bw-font-family)",
|
|
6210
|
-
}, children: ["\u26A0\uFE0F ", error] })), jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
|
|
6211
|
-
width: "100%",
|
|
6212
|
-
padding: "12px 24px",
|
|
6213
|
-
backgroundColor: "var(--bw-highlight-color)",
|
|
6214
|
-
color: "#ffffff",
|
|
6215
|
-
border: "none",
|
|
6216
|
-
borderRadius: "var(--bw-border-radius)",
|
|
6217
|
-
fontSize: "16px",
|
|
6218
|
-
fontWeight: 600,
|
|
6219
|
-
fontFamily: "var(--bw-font-family)",
|
|
6220
|
-
transition: "all 0.2s ease",
|
|
6221
|
-
display: "flex",
|
|
6222
|
-
alignItems: "center",
|
|
6223
|
-
justifyContent: "center",
|
|
6224
|
-
gap: "8px",
|
|
6225
|
-
cursor: isLoading ? "not-allowed" : "pointer",
|
|
6226
|
-
opacity: isLoading ? 0.6 : 1,
|
|
6227
|
-
}, children: isLoading ? (jsxs(Fragment, { children: [jsx("div", { style: {
|
|
6228
|
-
width: "16px",
|
|
6229
|
-
height: "16px",
|
|
6230
|
-
border: "2px solid #ffffff",
|
|
6231
|
-
borderTopColor: "transparent",
|
|
6232
|
-
borderRadius: "50%",
|
|
6233
|
-
animation: "spin 1s linear infinite",
|
|
6234
|
-
} }), t("button.creatingBooking")] })) : (t("button.bookWithGiftCard")) })] }));
|
|
6235
|
-
}
|
|
6236
6233
|
// Inner component that uses the Stripe hooks
|
|
6237
6234
|
function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSuccess, onError, }) {
|
|
6238
6235
|
const t = useTranslations();
|
|
@@ -6269,7 +6266,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSucce
|
|
|
6269
6266
|
redirect: "if_required",
|
|
6270
6267
|
});
|
|
6271
6268
|
if (error) {
|
|
6272
|
-
console.error("[
|
|
6269
|
+
console.error("[STRIPE_PAYMENT] Payment confirmation error:", {
|
|
6273
6270
|
type: error.type,
|
|
6274
6271
|
code: error.code,
|
|
6275
6272
|
message: error.message,
|
|
@@ -6345,8 +6342,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, onSucce
|
|
|
6345
6342
|
? t("button.depositAndBook")
|
|
6346
6343
|
: t("button.bookNow") })) })] }));
|
|
6347
6344
|
}
|
|
6348
|
-
|
|
6349
|
-
function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
|
|
6345
|
+
function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
|
|
6350
6346
|
const t = useTranslations();
|
|
6351
6347
|
const [clientSecret, setClientSecret] = useState(null);
|
|
6352
6348
|
const [paymentIntentId, setPaymentIntentId] = useState(null);
|
|
@@ -6407,7 +6403,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6407
6403
|
}
|
|
6408
6404
|
}, [paymentIntentId]);
|
|
6409
6405
|
useEffect(() => {
|
|
6410
|
-
const
|
|
6406
|
+
const createStripePayment = async () => {
|
|
6411
6407
|
if (!systemConfig || !eventDetails || !formData.participants?.length) {
|
|
6412
6408
|
return;
|
|
6413
6409
|
}
|
|
@@ -6448,35 +6444,38 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6448
6444
|
if (!requestData.customerEmail) {
|
|
6449
6445
|
throw new Error("Customer email is required");
|
|
6450
6446
|
}
|
|
6451
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-payment
|
|
6447
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
|
|
6452
6448
|
method: "POST",
|
|
6453
6449
|
headers: createApiHeaders(config),
|
|
6454
6450
|
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
6455
6451
|
});
|
|
6456
6452
|
const data = await response.json();
|
|
6457
6453
|
if (response.ok) {
|
|
6454
|
+
if (data.stalePaymentIntent) {
|
|
6455
|
+
clearPersistedPaymentIntent();
|
|
6456
|
+
}
|
|
6458
6457
|
setClientSecret(data.clientSecret);
|
|
6459
6458
|
setPaymentIntentId(data.paymentIntentId);
|
|
6460
6459
|
}
|
|
6461
6460
|
else {
|
|
6462
|
-
console.error("[
|
|
6461
|
+
console.error("[STRIPE_PAYMENT] Payment creation failed:", {
|
|
6463
6462
|
status: response.status,
|
|
6464
6463
|
error: data.error,
|
|
6465
6464
|
details: data.details,
|
|
6466
6465
|
requestData: { ...requestData, customerEmail: "[redacted]" },
|
|
6467
6466
|
});
|
|
6468
|
-
setPaymentError(data.error || t("error.
|
|
6467
|
+
setPaymentError(data.error || t("error.createPayment"));
|
|
6469
6468
|
}
|
|
6470
6469
|
}
|
|
6471
6470
|
catch (err) {
|
|
6472
|
-
console.error("[
|
|
6473
|
-
setPaymentError(err.message || t("error.
|
|
6471
|
+
console.error("[STRIPE_PAYMENT] Payment creation error:", err);
|
|
6472
|
+
setPaymentError(err.message || t("error.createPayment"));
|
|
6474
6473
|
}
|
|
6475
6474
|
finally {
|
|
6476
6475
|
setIsCreatingPaymentIntent(false);
|
|
6477
6476
|
}
|
|
6478
6477
|
};
|
|
6479
|
-
const timer = setTimeout(
|
|
6478
|
+
const timer = setTimeout(createStripePayment, 500);
|
|
6480
6479
|
return () => clearTimeout(timer);
|
|
6481
6480
|
}, [
|
|
6482
6481
|
systemConfig,
|
|
@@ -6491,13 +6490,8 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6491
6490
|
config,
|
|
6492
6491
|
upsellSelections,
|
|
6493
6492
|
]);
|
|
6494
|
-
const
|
|
6495
|
-
const
|
|
6496
|
-
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
6497
|
-
: 0;
|
|
6498
|
-
const discountAmount = discountCode?.discountAmount || 0;
|
|
6499
|
-
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
6500
|
-
const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
6493
|
+
const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
|
|
6494
|
+
const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
|
|
6501
6495
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
6502
6496
|
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6503
6497
|
}
|
|
@@ -11007,13 +11001,6 @@ function mergeStyles(...styles) {
|
|
|
11007
11001
|
return Object.assign({}, ...styles.filter(Boolean));
|
|
11008
11002
|
}
|
|
11009
11003
|
|
|
11010
|
-
function getEffectivePrice(upsell, round) {
|
|
11011
|
-
const dp = upsell.discountPercent ?? 0;
|
|
11012
|
-
if (dp <= 0)
|
|
11013
|
-
return upsell.price;
|
|
11014
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
11015
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
11016
|
-
}
|
|
11017
11004
|
// Local style aliases from shared styles
|
|
11018
11005
|
const cardStyles = cardStyles$1.base;
|
|
11019
11006
|
const sectionHeaderStyles = sectionStyles.header;
|
|
@@ -11025,6 +11012,11 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11025
11012
|
const { locale } = useLocale();
|
|
11026
11013
|
const timezone = useTimezone();
|
|
11027
11014
|
const roundEnabled = systemConfig?.roundPricesEnabled !== false;
|
|
11015
|
+
const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
|
|
11016
|
+
const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
|
|
11017
|
+
const raw = Math.round((baseAmount * basisPoints) / 10000);
|
|
11018
|
+
return round ? roundDiscountUp(raw) : raw;
|
|
11019
|
+
};
|
|
11028
11020
|
const [appliedVouchers, setAppliedVouchers] = useState([]);
|
|
11029
11021
|
const paymentSectionRef = useRef(null);
|
|
11030
11022
|
// Payment option: "deposit" or "full" - only relevant when deposit is available
|
|
@@ -11097,7 +11089,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11097
11089
|
participantUpsellIds.forEach(upsellId => {
|
|
11098
11090
|
const upsell = upsells.find(u => u.id === upsellId);
|
|
11099
11091
|
if (upsell) {
|
|
11100
|
-
total +=
|
|
11092
|
+
total += upsell.price;
|
|
11101
11093
|
}
|
|
11102
11094
|
});
|
|
11103
11095
|
}
|
|
@@ -11202,7 +11194,10 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11202
11194
|
// TODO: discounts currently apply to base total only; extend when discount-applies-to-upsells flag is added
|
|
11203
11195
|
let newDiscountAmount = 0;
|
|
11204
11196
|
if (voucher.discountType === "percentage") {
|
|
11205
|
-
newDiscountAmount =
|
|
11197
|
+
newDiscountAmount = calcPercentDiscountAmount(newBaseTotal, voucher.discountValue || 0, roundEnabled);
|
|
11198
|
+
if (voucher.restrictions?.maxDiscount) {
|
|
11199
|
+
newDiscountAmount = Math.min(newDiscountAmount, voucher.restrictions.maxDiscount);
|
|
11200
|
+
}
|
|
11206
11201
|
}
|
|
11207
11202
|
else {
|
|
11208
11203
|
newDiscountAmount = voucher.discountValue || 0;
|
|
@@ -11380,7 +11375,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11380
11375
|
padding: 0,
|
|
11381
11376
|
}, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
|
|
11382
11377
|
const isSelected = (participantUpsells[index] || []).includes(upsell.id);
|
|
11383
|
-
return (jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(
|
|
11378
|
+
return (jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
|
|
11384
11379
|
}) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsx("div", { style: {
|
|
11385
11380
|
display: "flex",
|
|
11386
11381
|
flexDirection: "column",
|
|
@@ -11412,7 +11407,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11412
11407
|
const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
|
|
11413
11408
|
if (countWithUpsell === 0)
|
|
11414
11409
|
return null;
|
|
11415
|
-
const upsellLineTotal =
|
|
11410
|
+
const upsellLineTotal = upsell.price * countWithUpsell;
|
|
11416
11411
|
return (jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "13px" }, children: [jsxs("span", { style: { color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+ ", upsell.name, " (", countWithUpsell, "\u00D7)"] }), jsx("span", { style: { color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: formatCurrency(upsellLineTotal) })] }, upsell.id));
|
|
11417
11412
|
})] })), appliedVouchers.length > 0 && (jsxs(Fragment, { children: [jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("summary.subtotal") }), jsx("span", { style: {
|
|
11418
11413
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -11557,7 +11552,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11557
11552
|
if (systemConfig?.paymentProvider === "mollie") {
|
|
11558
11553
|
return (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), 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 })] }));
|
|
11559
11554
|
}
|
|
11560
|
-
return (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsx(
|
|
11555
|
+
return (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), 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() })] }));
|
|
11561
11556
|
})() })] })] })] }) }));
|
|
11562
11557
|
}
|
|
11563
11558
|
|
|
@@ -11691,7 +11686,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11691
11686
|
if (targetPaymentIntentId) {
|
|
11692
11687
|
setIsLoading(true);
|
|
11693
11688
|
try {
|
|
11694
|
-
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment
|
|
11689
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
|
|
11695
11690
|
method: "POST",
|
|
11696
11691
|
headers: createApiHeaders(config),
|
|
11697
11692
|
body: JSON.stringify(createRequestBody(config, {
|
|
@@ -11707,7 +11702,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11707
11702
|
orderItems: data.orderItems,
|
|
11708
11703
|
purchases: data.purchases || [],
|
|
11709
11704
|
discount: data.discount || null,
|
|
11710
|
-
|
|
11705
|
+
providerPaymentDetails: data.stripePaymentIntent,
|
|
11711
11706
|
});
|
|
11712
11707
|
setEventDetails({
|
|
11713
11708
|
id: data.booking.eventInstance.id,
|
|
@@ -11715,7 +11710,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11715
11710
|
description: data.booking.eventInstance.eventType.description,
|
|
11716
11711
|
startTime: data.booking.eventInstance.startTime,
|
|
11717
11712
|
endTime: data.booking.eventInstance.endTime,
|
|
11718
|
-
price: data.booking.eventInstance.price || 0,
|
|
11713
|
+
price: data.booking.eventInstance.price || 0,
|
|
11719
11714
|
});
|
|
11720
11715
|
setFormData({
|
|
11721
11716
|
customerEmail: data.booking.customerEmail,
|
|
@@ -11723,9 +11718,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11723
11718
|
customerPhone: data.booking.customerPhone,
|
|
11724
11719
|
participants: data.booking.participants || [],
|
|
11725
11720
|
});
|
|
11726
|
-
// Set payment status from Stripe data or order status
|
|
11727
11721
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
11728
|
-
// Trigger Google Ads conversion tracking if payment is successful
|
|
11729
11722
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
11730
11723
|
if (finalPaymentStatus === "succeeded" &&
|
|
11731
11724
|
config.googleAds?.tagId &&
|
|
@@ -13675,11 +13668,10 @@ function formatUnavailableReason(reason, t) {
|
|
|
13675
13668
|
return t("upsells.reason.notEnoughSpots", { eventTypeName: reason.eventTypeName });
|
|
13676
13669
|
}
|
|
13677
13670
|
}
|
|
13678
|
-
function UpsellCard({ upsell, isSelected, participantCount, onSelect,
|
|
13671
|
+
function UpsellCard({ upsell, isSelected, participantCount, onSelect, }) {
|
|
13679
13672
|
const t = useTranslations();
|
|
13680
13673
|
const { locale } = useLocale();
|
|
13681
|
-
const
|
|
13682
|
-
const totalPrice = effectivePrice * participantCount;
|
|
13674
|
+
const totalPrice = upsell.price * participantCount;
|
|
13683
13675
|
const isDisabled = !upsell.available;
|
|
13684
13676
|
const getCardStyles = () => {
|
|
13685
13677
|
if (isDisabled)
|
|
@@ -13697,19 +13689,12 @@ function UpsellCard({ upsell, isSelected, participantCount, onSelect, roundPrice
|
|
|
13697
13689
|
weekday: "short",
|
|
13698
13690
|
day: "numeric",
|
|
13699
13691
|
month: "short",
|
|
13700
|
-
})] }), jsx("span", { style: { color: "var(--bw-text-muted)" }, children: t("upsells.spotsFree", { count: upsell.suggestedEventInstance.availableSpots }) })] }))] }), jsxs("div", { style: priceContainerStyles, children: [jsxs("span", { style: pricePerPersonStyles, children: [formatCurrency(
|
|
13692
|
+
})] }), jsx("span", { style: { color: "var(--bw-text-muted)" }, children: t("upsells.spotsFree", { count: upsell.suggestedEventInstance.availableSpots }) })] }))] }), jsxs("div", { style: priceContainerStyles, children: [jsxs("span", { style: pricePerPersonStyles, children: [formatCurrency(upsell.price), "/", t("common.perPerson")] }), participantCount > 1 && (jsxs("span", { style: priceTotalStyles, children: ["= ", formatCurrency(totalPrice)] }))] }), isDisabled && (jsx("div", { style: unavailableOverlayStyles, children: jsx("span", { children: upsell.unavailableReason
|
|
13701
13693
|
? formatUnavailableReason(upsell.unavailableReason, t)
|
|
13702
13694
|
: t("upsells.notAvailable") }) }))] }));
|
|
13703
13695
|
}
|
|
13704
13696
|
|
|
13705
|
-
function
|
|
13706
|
-
const dp = upsell.discountPercent ?? 0;
|
|
13707
|
-
if (dp <= 0)
|
|
13708
|
-
return upsell.price;
|
|
13709
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
13710
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
13711
|
-
}
|
|
13712
|
-
function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, isOpen, onClose, onSelect, onContinue, onBack, roundPricesEnabled = true, }) {
|
|
13697
|
+
function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, isOpen, onClose, onSelect, onContinue, onBack, }) {
|
|
13713
13698
|
const t = useTranslations();
|
|
13714
13699
|
const selectUpsell = (upsellId) => {
|
|
13715
13700
|
const exists = selectedUpsells.find((s) => s.upsellPackageId === upsellId);
|
|
@@ -13728,7 +13713,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13728
13713
|
return selectedUpsells.reduce((total, selection) => {
|
|
13729
13714
|
const upsell = upsells.find((u) => u.id === selection.upsellPackageId);
|
|
13730
13715
|
if (upsell) {
|
|
13731
|
-
return total +
|
|
13716
|
+
return total + upsell.price * selection.quantity;
|
|
13732
13717
|
}
|
|
13733
13718
|
return total;
|
|
13734
13719
|
}, 0);
|
|
@@ -13736,7 +13721,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13736
13721
|
const selectedTotal = calculateTotal();
|
|
13737
13722
|
const selectedCount = selectedUpsells.length;
|
|
13738
13723
|
const footerContent = (jsxs(Fragment, { children: [jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), children: t("common.back") }), jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
|
|
13739
|
-
return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id)
|
|
13724
|
+
return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (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: [jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
|
|
13740
13725
|
}
|
|
13741
13726
|
|
|
13742
13727
|
// Main widget component
|
|
@@ -13782,7 +13767,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13782
13767
|
const [isLoadingShowAll, setIsLoadingShowAll] = useState(false);
|
|
13783
13768
|
const [error, setError] = useState(null);
|
|
13784
13769
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
13785
|
-
const [
|
|
13770
|
+
const [successPaymentId, setSuccessPaymentId] = useState(null);
|
|
13786
13771
|
const [systemConfig, setSystemConfig] = useState(null);
|
|
13787
13772
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
13788
13773
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
@@ -13848,7 +13833,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13848
13833
|
const stripeReturn = detectStripeReturn();
|
|
13849
13834
|
if (stripeReturn) {
|
|
13850
13835
|
if (stripeReturn.redirectStatus === "succeeded") {
|
|
13851
|
-
|
|
13836
|
+
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
13852
13837
|
setIsSuccess(true);
|
|
13853
13838
|
}
|
|
13854
13839
|
else if (stripeReturn.redirectStatus === "failed") {
|
|
@@ -14203,7 +14188,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14203
14188
|
};
|
|
14204
14189
|
const handleBookingSuccess = (result) => {
|
|
14205
14190
|
setIsSuccess(true);
|
|
14206
|
-
|
|
14191
|
+
setSuccessPaymentId(result.paymentIntent.id);
|
|
14207
14192
|
setSidebarOpen(false);
|
|
14208
14193
|
setShouldRenderBookingForm(false);
|
|
14209
14194
|
config.onSuccess?.(result);
|
|
@@ -14401,7 +14386,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14401
14386
|
setIsSuccess(false);
|
|
14402
14387
|
setCurrentStep("eventTypes");
|
|
14403
14388
|
setShowingPreview(true);
|
|
14404
|
-
|
|
14389
|
+
setSuccessPaymentId(null);
|
|
14405
14390
|
setShouldRenderInstanceSelection(false);
|
|
14406
14391
|
setShouldRenderUpsells(false);
|
|
14407
14392
|
setShouldRenderBookingForm(false);
|
|
@@ -14412,7 +14397,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14412
14397
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14413
14398
|
url.searchParams.delete("redirect_status");
|
|
14414
14399
|
window.history.replaceState({}, "", url.toString());
|
|
14415
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14400
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14416
14401
|
}
|
|
14417
14402
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
14418
14403
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -14425,7 +14410,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14425
14410
|
setIsSuccess(false);
|
|
14426
14411
|
setCurrentStep("eventTypes");
|
|
14427
14412
|
setShowingPreview(true);
|
|
14428
|
-
|
|
14413
|
+
setSuccessPaymentId(null);
|
|
14429
14414
|
setShouldRenderInstanceSelection(false);
|
|
14430
14415
|
setShouldRenderBookingForm(false);
|
|
14431
14416
|
const url = new URL(window.location.href);
|
|
@@ -14433,7 +14418,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14433
14418
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14434
14419
|
url.searchParams.delete("redirect_status");
|
|
14435
14420
|
window.history.replaceState({}, "", url.toString());
|
|
14436
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14421
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14437
14422
|
}
|
|
14438
14423
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
14439
14424
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -14462,11 +14447,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14462
14447
|
setShouldRenderInstanceSelection(true);
|
|
14463
14448
|
}
|
|
14464
14449
|
}, children: config.buttonText ||
|
|
14465
|
-
(isDirectInstanceMode ? t("button.bookNow") : t("button.selectDate")) }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack
|
|
14450
|
+
(isDirectInstanceMode ? t("button.bookNow") : t("button.selectDate")) }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, 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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
14466
14451
|
setIsSuccess(false);
|
|
14467
14452
|
setCurrentStep("eventTypes");
|
|
14468
14453
|
setSidebarOpen(false);
|
|
14469
|
-
|
|
14454
|
+
setSuccessPaymentId(null);
|
|
14470
14455
|
setShouldRenderInstanceSelection(false);
|
|
14471
14456
|
setShouldRenderUpsells(false);
|
|
14472
14457
|
setShouldRenderBookingForm(false);
|
|
@@ -14477,7 +14462,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14477
14462
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14478
14463
|
url.searchParams.delete("redirect_status");
|
|
14479
14464
|
window.history.replaceState({}, "", url.toString());
|
|
14480
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14465
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14481
14466
|
}
|
|
14482
14467
|
// Cards mode (default) - show event type selection
|
|
14483
14468
|
const cardsView = (jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount() }));
|
|
@@ -14509,10 +14494,10 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14509
14494
|
};
|
|
14510
14495
|
};
|
|
14511
14496
|
const backHandlers = getBackHandlers();
|
|
14512
|
-
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack
|
|
14497
|
+
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, 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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
14513
14498
|
setIsSuccess(false);
|
|
14514
14499
|
setCurrentStep("eventTypes");
|
|
14515
|
-
|
|
14500
|
+
setSuccessPaymentId(null);
|
|
14516
14501
|
setShouldRenderInstanceSelection(false);
|
|
14517
14502
|
setShouldRenderUpsells(false);
|
|
14518
14503
|
setShouldRenderBookingForm(false);
|
|
@@ -14523,7 +14508,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14523
14508
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14524
14509
|
url.searchParams.delete("redirect_status");
|
|
14525
14510
|
window.history.replaceState({}, "", url.toString());
|
|
14526
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14511
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14527
14512
|
}
|
|
14528
14513
|
function UniversalBookingWidget(props) {
|
|
14529
14514
|
const [languagePolicy, setLanguagePolicy] = useState(null);
|