@bigz-app/booking-widget 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/booking-widget.js +176 -181
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- package/dist/components/booking/BookingForm.d.ts +0 -1
- package/dist/components/booking/BookingForm.d.ts.map +1 -1
- package/dist/components/booking/BookingSuccessModal.d.ts.map +1 -1
- package/dist/components/booking/GiftCardOnlyBooking.d.ts +22 -0
- package/dist/components/booking/GiftCardOnlyBooking.d.ts.map +1 -0
- package/dist/components/booking/MolliePaymentForm.d.ts +2 -6
- package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
- package/dist/components/booking/{PaymentForm.d.ts → StripePaymentForm.d.ts} +4 -8
- package/dist/components/booking/StripePaymentForm.d.ts.map +1 -0
- package/dist/components/booking/VoucherInput.d.ts +8 -0
- package/dist/components/booking/VoucherInput.d.ts.map +1 -1
- package/dist/components/booking/index.d.ts +2 -1
- package/dist/components/booking/index.d.ts.map +1 -1
- package/dist/components/upsells/UpsellCard.d.ts +1 -2
- package/dist/components/upsells/UpsellCard.d.ts.map +1 -1
- package/dist/components/upsells/UpsellsStep.d.ts +1 -4
- package/dist/components/upsells/UpsellsStep.d.ts.map +1 -1
- package/dist/components/upsells/index.d.ts +1 -1
- package/dist/components/upsells/index.d.ts.map +1 -1
- package/dist/index.cjs +176 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +176 -181
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/booking/PaymentForm.d.ts.map +0 -1
package/dist/index.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,
|
|
@@ -6484,19 +6483,15 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
6484
6483
|
formData.participants,
|
|
6485
6484
|
formData.customerEmail,
|
|
6486
6485
|
formData.customerName,
|
|
6486
|
+
formData.comment,
|
|
6487
6487
|
totalAmount,
|
|
6488
6488
|
discountCode,
|
|
6489
6489
|
giftCards,
|
|
6490
6490
|
config,
|
|
6491
6491
|
upsellSelections,
|
|
6492
6492
|
]);
|
|
6493
|
-
const
|
|
6494
|
-
const
|
|
6495
|
-
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
6496
|
-
: 0;
|
|
6497
|
-
const discountAmount = discountCode?.discountAmount || 0;
|
|
6498
|
-
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
6499
|
-
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);
|
|
6500
6495
|
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
6501
6496
|
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
|
|
6502
6497
|
}
|
|
@@ -11006,13 +11001,6 @@ function mergeStyles(...styles) {
|
|
|
11006
11001
|
return Object.assign({}, ...styles.filter(Boolean));
|
|
11007
11002
|
}
|
|
11008
11003
|
|
|
11009
|
-
function getEffectivePrice(upsell, round) {
|
|
11010
|
-
const dp = upsell.discountPercent ?? 0;
|
|
11011
|
-
if (dp <= 0)
|
|
11012
|
-
return upsell.price;
|
|
11013
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
11014
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
11015
|
-
}
|
|
11016
11004
|
// Local style aliases from shared styles
|
|
11017
11005
|
const cardStyles = cardStyles$1.base;
|
|
11018
11006
|
const sectionHeaderStyles = sectionStyles.header;
|
|
@@ -11024,6 +11012,11 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11024
11012
|
const { locale } = useLocale();
|
|
11025
11013
|
const timezone = useTimezone();
|
|
11026
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
|
+
};
|
|
11027
11020
|
const [appliedVouchers, setAppliedVouchers] = useState([]);
|
|
11028
11021
|
const paymentSectionRef = useRef(null);
|
|
11029
11022
|
// Payment option: "deposit" or "full" - only relevant when deposit is available
|
|
@@ -11045,6 +11038,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11045
11038
|
const watchedParticipants = form.watch("participants");
|
|
11046
11039
|
const watchedCustomerName = form.watch("customerName");
|
|
11047
11040
|
const watchedCustomerEmail = form.watch("customerEmail");
|
|
11041
|
+
const watchedComment = form.watch("comment");
|
|
11042
|
+
const watchedCustomerPhone = form.watch("customerPhone");
|
|
11048
11043
|
const customerNameError = form.formState.errors.customerName;
|
|
11049
11044
|
const customerEmailError = form.formState.errors.customerEmail;
|
|
11050
11045
|
const watchedAcceptTerms = form.watch("acceptTerms");
|
|
@@ -11094,7 +11089,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11094
11089
|
participantUpsellIds.forEach(upsellId => {
|
|
11095
11090
|
const upsell = upsells.find(u => u.id === upsellId);
|
|
11096
11091
|
if (upsell) {
|
|
11097
|
-
total +=
|
|
11092
|
+
total += upsell.price;
|
|
11098
11093
|
}
|
|
11099
11094
|
});
|
|
11100
11095
|
}
|
|
@@ -11156,6 +11151,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11156
11151
|
participantIndices,
|
|
11157
11152
|
}));
|
|
11158
11153
|
}, [participantUpsells, watchedParticipants]);
|
|
11154
|
+
const paymentFormData = useMemo(() => ({
|
|
11155
|
+
customerName: watchedCustomerName,
|
|
11156
|
+
customerEmail: watchedCustomerEmail,
|
|
11157
|
+
customerPhone: watchedCustomerPhone,
|
|
11158
|
+
participants: watchedParticipants,
|
|
11159
|
+
comment: watchedComment,
|
|
11160
|
+
}), [watchedCustomerName, watchedCustomerEmail, watchedCustomerPhone, watchedParticipants, watchedComment]);
|
|
11159
11161
|
const appliedDiscountCode = appliedVouchers.find((v) => v.type === "discount");
|
|
11160
11162
|
const appliedGiftCards = appliedVouchers.filter((v) => v.type === "giftCard");
|
|
11161
11163
|
const handleVoucherValidated = useCallback((voucher, _error) => {
|
|
@@ -11192,7 +11194,10 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11192
11194
|
// TODO: discounts currently apply to base total only; extend when discount-applies-to-upsells flag is added
|
|
11193
11195
|
let newDiscountAmount = 0;
|
|
11194
11196
|
if (voucher.discountType === "percentage") {
|
|
11195
|
-
newDiscountAmount =
|
|
11197
|
+
newDiscountAmount = calcPercentDiscountAmount(newBaseTotal, voucher.discountValue || 0, roundEnabled);
|
|
11198
|
+
if (voucher.restrictions?.maxDiscount) {
|
|
11199
|
+
newDiscountAmount = Math.min(newDiscountAmount, voucher.restrictions.maxDiscount);
|
|
11200
|
+
}
|
|
11196
11201
|
}
|
|
11197
11202
|
else {
|
|
11198
11203
|
newDiscountAmount = voucher.discountValue || 0;
|
|
@@ -11370,7 +11375,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11370
11375
|
padding: 0,
|
|
11371
11376
|
}, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
|
|
11372
11377
|
const isSelected = (participantUpsells[index] || []).includes(upsell.id);
|
|
11373
|
-
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));
|
|
11374
11379
|
}) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsx("div", { style: {
|
|
11375
11380
|
display: "flex",
|
|
11376
11381
|
flexDirection: "column",
|
|
@@ -11402,7 +11407,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11402
11407
|
const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
|
|
11403
11408
|
if (countWithUpsell === 0)
|
|
11404
11409
|
return null;
|
|
11405
|
-
const upsellLineTotal =
|
|
11410
|
+
const upsellLineTotal = upsell.price * countWithUpsell;
|
|
11406
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));
|
|
11407
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: {
|
|
11408
11413
|
fontFamily: "var(--bw-font-family)",
|
|
@@ -11545,9 +11550,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11545
11550
|
}
|
|
11546
11551
|
: null;
|
|
11547
11552
|
if (systemConfig?.paymentProvider === "mollie") {
|
|
11548
|
-
return (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: { ...sectionHeaderStyles }, children: t$1("summary.payment") }), jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData:
|
|
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 })] }));
|
|
11549
11554
|
}
|
|
11550
|
-
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() })] }));
|
|
11551
11556
|
})() })] })] })] }) }));
|
|
11552
11557
|
}
|
|
11553
11558
|
|
|
@@ -11681,7 +11686,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11681
11686
|
if (targetPaymentIntentId) {
|
|
11682
11687
|
setIsLoading(true);
|
|
11683
11688
|
try {
|
|
11684
|
-
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"), {
|
|
11685
11690
|
method: "POST",
|
|
11686
11691
|
headers: createApiHeaders(config),
|
|
11687
11692
|
body: JSON.stringify(createRequestBody(config, {
|
|
@@ -11697,7 +11702,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11697
11702
|
orderItems: data.orderItems,
|
|
11698
11703
|
purchases: data.purchases || [],
|
|
11699
11704
|
discount: data.discount || null,
|
|
11700
|
-
|
|
11705
|
+
providerPaymentDetails: data.stripePaymentIntent,
|
|
11701
11706
|
});
|
|
11702
11707
|
setEventDetails({
|
|
11703
11708
|
id: data.booking.eventInstance.id,
|
|
@@ -11705,7 +11710,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11705
11710
|
description: data.booking.eventInstance.eventType.description,
|
|
11706
11711
|
startTime: data.booking.eventInstance.startTime,
|
|
11707
11712
|
endTime: data.booking.eventInstance.endTime,
|
|
11708
|
-
price: data.booking.eventInstance.price || 0,
|
|
11713
|
+
price: data.booking.eventInstance.price || 0,
|
|
11709
11714
|
});
|
|
11710
11715
|
setFormData({
|
|
11711
11716
|
customerEmail: data.booking.customerEmail,
|
|
@@ -11713,9 +11718,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
11713
11718
|
customerPhone: data.booking.customerPhone,
|
|
11714
11719
|
participants: data.booking.participants || [],
|
|
11715
11720
|
});
|
|
11716
|
-
// Set payment status from Stripe data or order status
|
|
11717
11721
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
11718
|
-
// Trigger Google Ads conversion tracking if payment is successful
|
|
11719
11722
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
11720
11723
|
if (finalPaymentStatus === "succeeded" &&
|
|
11721
11724
|
config.googleAds?.tagId &&
|
|
@@ -13665,11 +13668,10 @@ function formatUnavailableReason(reason, t) {
|
|
|
13665
13668
|
return t("upsells.reason.notEnoughSpots", { eventTypeName: reason.eventTypeName });
|
|
13666
13669
|
}
|
|
13667
13670
|
}
|
|
13668
|
-
function UpsellCard({ upsell, isSelected, participantCount, onSelect,
|
|
13671
|
+
function UpsellCard({ upsell, isSelected, participantCount, onSelect, }) {
|
|
13669
13672
|
const t = useTranslations();
|
|
13670
13673
|
const { locale } = useLocale();
|
|
13671
|
-
const
|
|
13672
|
-
const totalPrice = effectivePrice * participantCount;
|
|
13674
|
+
const totalPrice = upsell.price * participantCount;
|
|
13673
13675
|
const isDisabled = !upsell.available;
|
|
13674
13676
|
const getCardStyles = () => {
|
|
13675
13677
|
if (isDisabled)
|
|
@@ -13687,19 +13689,12 @@ function UpsellCard({ upsell, isSelected, participantCount, onSelect, roundPrice
|
|
|
13687
13689
|
weekday: "short",
|
|
13688
13690
|
day: "numeric",
|
|
13689
13691
|
month: "short",
|
|
13690
|
-
})] }), 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
|
|
13691
13693
|
? formatUnavailableReason(upsell.unavailableReason, t)
|
|
13692
13694
|
: t("upsells.notAvailable") }) }))] }));
|
|
13693
13695
|
}
|
|
13694
13696
|
|
|
13695
|
-
function
|
|
13696
|
-
const dp = upsell.discountPercent ?? 0;
|
|
13697
|
-
if (dp <= 0)
|
|
13698
|
-
return upsell.price;
|
|
13699
|
-
const raw = Math.round(upsell.price * (100 - dp) / 100);
|
|
13700
|
-
return round ? Math.floor(raw / 100) * 100 : raw;
|
|
13701
|
-
}
|
|
13702
|
-
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, }) {
|
|
13703
13698
|
const t = useTranslations();
|
|
13704
13699
|
const selectUpsell = (upsellId) => {
|
|
13705
13700
|
const exists = selectedUpsells.find((s) => s.upsellPackageId === upsellId);
|
|
@@ -13718,7 +13713,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13718
13713
|
return selectedUpsells.reduce((total, selection) => {
|
|
13719
13714
|
const upsell = upsells.find((u) => u.id === selection.upsellPackageId);
|
|
13720
13715
|
if (upsell) {
|
|
13721
|
-
return total +
|
|
13716
|
+
return total + upsell.price * selection.quantity;
|
|
13722
13717
|
}
|
|
13723
13718
|
return total;
|
|
13724
13719
|
}, 0);
|
|
@@ -13726,7 +13721,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
13726
13721
|
const selectedTotal = calculateTotal();
|
|
13727
13722
|
const selectedCount = selectedUpsells.length;
|
|
13728
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") })] }));
|
|
13729
|
-
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)] })] }))] }) }));
|
|
13730
13725
|
}
|
|
13731
13726
|
|
|
13732
13727
|
// Main widget component
|
|
@@ -13772,7 +13767,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13772
13767
|
const [isLoadingShowAll, setIsLoadingShowAll] = useState(false);
|
|
13773
13768
|
const [error, setError] = useState(null);
|
|
13774
13769
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
13775
|
-
const [
|
|
13770
|
+
const [successPaymentId, setSuccessPaymentId] = useState(null);
|
|
13776
13771
|
const [systemConfig, setSystemConfig] = useState(null);
|
|
13777
13772
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
13778
13773
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
@@ -13838,7 +13833,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
13838
13833
|
const stripeReturn = detectStripeReturn();
|
|
13839
13834
|
if (stripeReturn) {
|
|
13840
13835
|
if (stripeReturn.redirectStatus === "succeeded") {
|
|
13841
|
-
|
|
13836
|
+
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
13842
13837
|
setIsSuccess(true);
|
|
13843
13838
|
}
|
|
13844
13839
|
else if (stripeReturn.redirectStatus === "failed") {
|
|
@@ -14193,7 +14188,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14193
14188
|
};
|
|
14194
14189
|
const handleBookingSuccess = (result) => {
|
|
14195
14190
|
setIsSuccess(true);
|
|
14196
|
-
|
|
14191
|
+
setSuccessPaymentId(result.paymentIntent.id);
|
|
14197
14192
|
setSidebarOpen(false);
|
|
14198
14193
|
setShouldRenderBookingForm(false);
|
|
14199
14194
|
config.onSuccess?.(result);
|
|
@@ -14391,7 +14386,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14391
14386
|
setIsSuccess(false);
|
|
14392
14387
|
setCurrentStep("eventTypes");
|
|
14393
14388
|
setShowingPreview(true);
|
|
14394
|
-
|
|
14389
|
+
setSuccessPaymentId(null);
|
|
14395
14390
|
setShouldRenderInstanceSelection(false);
|
|
14396
14391
|
setShouldRenderUpsells(false);
|
|
14397
14392
|
setShouldRenderBookingForm(false);
|
|
@@ -14402,7 +14397,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14402
14397
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14403
14398
|
url.searchParams.delete("redirect_status");
|
|
14404
14399
|
window.history.replaceState({}, "", url.toString());
|
|
14405
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14400
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14406
14401
|
}
|
|
14407
14402
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
14408
14403
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -14415,7 +14410,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14415
14410
|
setIsSuccess(false);
|
|
14416
14411
|
setCurrentStep("eventTypes");
|
|
14417
14412
|
setShowingPreview(true);
|
|
14418
|
-
|
|
14413
|
+
setSuccessPaymentId(null);
|
|
14419
14414
|
setShouldRenderInstanceSelection(false);
|
|
14420
14415
|
setShouldRenderBookingForm(false);
|
|
14421
14416
|
const url = new URL(window.location.href);
|
|
@@ -14423,7 +14418,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14423
14418
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14424
14419
|
url.searchParams.delete("redirect_status");
|
|
14425
14420
|
window.history.replaceState({}, "", url.toString());
|
|
14426
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14421
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14427
14422
|
}
|
|
14428
14423
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
14429
14424
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -14452,11 +14447,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14452
14447
|
setShouldRenderInstanceSelection(true);
|
|
14453
14448
|
}
|
|
14454
14449
|
}, children: config.buttonText ||
|
|
14455
|
-
(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: () => {
|
|
14456
14451
|
setIsSuccess(false);
|
|
14457
14452
|
setCurrentStep("eventTypes");
|
|
14458
14453
|
setSidebarOpen(false);
|
|
14459
|
-
|
|
14454
|
+
setSuccessPaymentId(null);
|
|
14460
14455
|
setShouldRenderInstanceSelection(false);
|
|
14461
14456
|
setShouldRenderUpsells(false);
|
|
14462
14457
|
setShouldRenderBookingForm(false);
|
|
@@ -14467,7 +14462,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14467
14462
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14468
14463
|
url.searchParams.delete("redirect_status");
|
|
14469
14464
|
window.history.replaceState({}, "", url.toString());
|
|
14470
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14465
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14471
14466
|
}
|
|
14472
14467
|
// Cards mode (default) - show event type selection
|
|
14473
14468
|
const cardsView = (jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount() }));
|
|
@@ -14499,10 +14494,10 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14499
14494
|
};
|
|
14500
14495
|
};
|
|
14501
14496
|
const backHandlers = getBackHandlers();
|
|
14502
|
-
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: () => {
|
|
14503
14498
|
setIsSuccess(false);
|
|
14504
14499
|
setCurrentStep("eventTypes");
|
|
14505
|
-
|
|
14500
|
+
setSuccessPaymentId(null);
|
|
14506
14501
|
setShouldRenderInstanceSelection(false);
|
|
14507
14502
|
setShouldRenderUpsells(false);
|
|
14508
14503
|
setShouldRenderBookingForm(false);
|
|
@@ -14513,7 +14508,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
14513
14508
|
url.searchParams.delete("payment_intent_client_secret");
|
|
14514
14509
|
url.searchParams.delete("redirect_status");
|
|
14515
14510
|
window.history.replaceState({}, "", url.toString());
|
|
14516
|
-
}, config: config, onError: setError, paymentIntentId:
|
|
14511
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
14517
14512
|
}
|
|
14518
14513
|
function UniversalBookingWidget(props) {
|
|
14519
14514
|
const [languagePolicy, setLanguagePolicy] = useState(null);
|