@bigz-app/booking-widget 0.3.6 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/booking-widget.js +1016 -300
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/BookingForm.d.ts.map +1 -1
- package/dist/components/EventInstanceSelection.d.ts.map +1 -1
- package/dist/components/PaymentForm.d.ts +3 -1
- package/dist/components/PaymentForm.d.ts.map +1 -1
- package/dist/components/PromoDialog.d.ts +7 -0
- package/dist/components/PromoDialog.d.ts.map +1 -0
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- package/dist/components/VoucherInput.d.ts +29 -0
- package/dist/components/VoucherInput.d.ts.map +1 -0
- package/dist/index.cjs +1017 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1018 -302
- package/dist/index.esm.js.map +1 -1
- package/package.json +77 -70
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React__default from 'react';
|
|
2
|
-
import React__default__default, { createContext, useState, useEffect, useMemo, useContext, useRef } from 'react';
|
|
2
|
+
import React__default__default, { createContext, useState, useEffect, useMemo, useContext, useRef, useCallback, Fragment as Fragment$1 } from 'react';
|
|
3
3
|
import { createRoot } from 'react-dom/client';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
@@ -6888,7 +6888,7 @@ const preprocessMarkdown$1 = (markdown) => {
|
|
|
6888
6888
|
// Convert double underscores to HTML underline tags for React Markdown
|
|
6889
6889
|
return markdown.replace(/__([^_]+)__/g, "<u>$1</u>");
|
|
6890
6890
|
};
|
|
6891
|
-
const IconCheck$
|
|
6891
|
+
const IconCheck$2 = ({ size = 16, color = "#10b981" }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
6892
6892
|
const IconWave$1 = ({ size = 20, color = "#0ea5e9" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M2 18c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsx("path", { d: "M2 12c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsx("path", { d: "M2 6c2-2 6-2 8 0s6 2 8 0 6-2 8 0" })] }));
|
|
6893
6893
|
function EventTypeDetailsDialog({ isOpen, onClose, eventType, onEventTypeSelect, }) {
|
|
6894
6894
|
if (!isOpen || !eventType)
|
|
@@ -6935,7 +6935,7 @@ function EventTypeDetailsDialog({ isOpen, onClose, eventType, onEventTypeSelect,
|
|
|
6935
6935
|
fontSize: "16px",
|
|
6936
6936
|
lineHeight: "1.6",
|
|
6937
6937
|
color: "var(--bw-text-color)",
|
|
6938
|
-
}, children: [jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsx(IconCheck$
|
|
6938
|
+
}, children: [jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsx(IconCheck$2, { size: 16, color: "var(--bw-success-color)" }) }), jsx("span", { children: highlight.trim() })] }, index))) }) }) }))] }), eventType.description && (jsxs("div", { style: {
|
|
6939
6939
|
marginBottom: "24px",
|
|
6940
6940
|
color: "var(--bw-text-muted)",
|
|
6941
6941
|
fontSize: "16px",
|
|
@@ -7244,7 +7244,7 @@ const preprocessMarkdown = (markdown) => {
|
|
|
7244
7244
|
// Custom minimal SVG icons (Lucide-style)
|
|
7245
7245
|
const IconClock = ({ size = 16, color = "#10b981" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("circle", { cx: "12", cy: "12", r: "10" }), jsx("polyline", { points: "12 6 12 12 16 14" })] }));
|
|
7246
7246
|
const IconCalendar = ({ size = 16, color = "#3b82f6" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }), jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }), jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }), jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" })] }));
|
|
7247
|
-
const IconCheck = ({ size = 16, color = "#10b981" }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
7247
|
+
const IconCheck$1 = ({ size = 16, color = "#10b981" }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
7248
7248
|
// Wave icon for booking action
|
|
7249
7249
|
const IconWave = ({ size = 20, color = "#0ea5e9" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M2 18c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsx("path", { d: "M2 12c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsx("path", { d: "M2 6c2-2 6-2 8 0s6 2 8 0 6-2 8 0" })] }));
|
|
7250
7250
|
// Loading skeleton component that matches the actual design
|
|
@@ -7708,7 +7708,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
|
|
|
7708
7708
|
color: "var(--bw-text-muted)",
|
|
7709
7709
|
position: "relative",
|
|
7710
7710
|
maxWidth: "100%",
|
|
7711
|
-
}, children: [jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsx(IconCheck, { size: 16, color: "var(--bw-success-color)" }) }), jsx("span", { style: {
|
|
7711
|
+
}, children: [jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsx(IconCheck$1, { size: 16, color: "var(--bw-success-color)" }) }), jsx("span", { style: {
|
|
7712
7712
|
textOverflow: "ellipsis",
|
|
7713
7713
|
overflow: "hidden",
|
|
7714
7714
|
whiteSpace: "nowrap",
|
|
@@ -9111,6 +9111,110 @@ var reactStripe_umd = {exports: {}};
|
|
|
9111
9111
|
|
|
9112
9112
|
var reactStripe_umdExports = reactStripe_umd.exports;
|
|
9113
9113
|
|
|
9114
|
+
// Component for bookings fully covered by gift cards (no Stripe payment needed)
|
|
9115
|
+
function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, }) {
|
|
9116
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
9117
|
+
const [error, setError] = useState(null);
|
|
9118
|
+
const handleBooking = async () => {
|
|
9119
|
+
setIsLoading(true);
|
|
9120
|
+
setError(null);
|
|
9121
|
+
try {
|
|
9122
|
+
// Create booking directly without Stripe payment
|
|
9123
|
+
const requestData = {
|
|
9124
|
+
eventInstanceId: config.eventInstanceId || eventDetails.id,
|
|
9125
|
+
organizationId: config.organizationId,
|
|
9126
|
+
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
9127
|
+
discountCode: discountCode?.code,
|
|
9128
|
+
giftCardCodes: giftCards.map((gc) => gc.code),
|
|
9129
|
+
customerName: formData.customerName?.trim(),
|
|
9130
|
+
customerEmail: formData.customerEmail?.trim(),
|
|
9131
|
+
customerPhone: formData.customerPhone?.trim(),
|
|
9132
|
+
comment: formData.comment?.trim(),
|
|
9133
|
+
paymentMethod: "gift_card",
|
|
9134
|
+
};
|
|
9135
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
|
|
9136
|
+
method: "POST",
|
|
9137
|
+
headers: createApiHeaders(config),
|
|
9138
|
+
body: JSON.stringify(createRequestBody(config, requestData)),
|
|
9139
|
+
});
|
|
9140
|
+
const data = await response.json();
|
|
9141
|
+
if (response.ok) {
|
|
9142
|
+
onSuccess({
|
|
9143
|
+
booking: data.booking,
|
|
9144
|
+
order: data.order,
|
|
9145
|
+
giftCardRedemptions: data.giftCardRedemptions,
|
|
9146
|
+
});
|
|
9147
|
+
}
|
|
9148
|
+
else {
|
|
9149
|
+
setError(data.error || "Fehler beim Erstellen der Buchung");
|
|
9150
|
+
onError(data.error || "Fehler beim Erstellen der Buchung");
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9153
|
+
catch (err) {
|
|
9154
|
+
setError(err.message || "Fehler beim Erstellen der Buchung");
|
|
9155
|
+
onError(err.message || "Fehler beim Erstellen der Buchung");
|
|
9156
|
+
}
|
|
9157
|
+
finally {
|
|
9158
|
+
setIsLoading(false);
|
|
9159
|
+
}
|
|
9160
|
+
};
|
|
9161
|
+
const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
|
|
9162
|
+
return (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing)" }, children: [jsxs("div", { style: {
|
|
9163
|
+
backgroundColor: "var(--bw-success-color)15",
|
|
9164
|
+
border: "1px solid var(--bw-success-color)40",
|
|
9165
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9166
|
+
padding: "var(--bw-spacing)",
|
|
9167
|
+
}, children: [jsx("div", { style: {
|
|
9168
|
+
display: "flex",
|
|
9169
|
+
alignItems: "center",
|
|
9170
|
+
gap: "8px",
|
|
9171
|
+
marginBottom: "8px",
|
|
9172
|
+
color: "var(--bw-success-color)",
|
|
9173
|
+
fontFamily: "var(--bw-font-family)",
|
|
9174
|
+
fontWeight: "600",
|
|
9175
|
+
}, children: "\uD83C\uDF81 Vollst\u00E4ndig durch Gutschein(e) gedeckt" }), jsxs("div", { style: {
|
|
9176
|
+
fontSize: "var(--bw-font-size)",
|
|
9177
|
+
color: "var(--bw-text-muted)",
|
|
9178
|
+
fontFamily: "var(--bw-font-family)",
|
|
9179
|
+
}, children: ["Gutschein-Guthaben: ", formatCurrency(totalGiftCardAmount)] })] }), error && (jsxs("div", { style: {
|
|
9180
|
+
backgroundColor: "var(--bw-error-color)15",
|
|
9181
|
+
border: "1px solid var(--bw-error-color)40",
|
|
9182
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9183
|
+
padding: "var(--bw-spacing)",
|
|
9184
|
+
color: "var(--bw-error-color)",
|
|
9185
|
+
fontSize: "var(--bw-font-size)",
|
|
9186
|
+
fontFamily: "var(--bw-font-family)",
|
|
9187
|
+
}, children: ["\u26A0\uFE0F ", error] })), jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
|
|
9188
|
+
width: "100%",
|
|
9189
|
+
padding: "12px 24px",
|
|
9190
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
9191
|
+
color: "#fff",
|
|
9192
|
+
border: "none",
|
|
9193
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9194
|
+
fontSize: "var(--bw-font-size)",
|
|
9195
|
+
fontWeight: "600",
|
|
9196
|
+
cursor: isLoading ? "not-allowed" : "pointer",
|
|
9197
|
+
opacity: isLoading ? 0.6 : 1,
|
|
9198
|
+
transition: "all 0.2s ease",
|
|
9199
|
+
fontFamily: "var(--bw-font-family)",
|
|
9200
|
+
display: "flex",
|
|
9201
|
+
alignItems: "center",
|
|
9202
|
+
justifyContent: "center",
|
|
9203
|
+
gap: "8px",
|
|
9204
|
+
}, children: isLoading ? (jsxs(Fragment, { children: [jsx("div", { style: {
|
|
9205
|
+
width: "16px",
|
|
9206
|
+
height: "16px",
|
|
9207
|
+
border: "2px solid #fff",
|
|
9208
|
+
borderTopColor: "transparent",
|
|
9209
|
+
borderRadius: "50%",
|
|
9210
|
+
animation: "spin 1s linear infinite",
|
|
9211
|
+
} }), "Buchung wird erstellt..."] })) : ("Mit Gutschein buchen") }), jsx("style", { children: `
|
|
9212
|
+
@keyframes spin {
|
|
9213
|
+
from { transform: rotate(0deg); }
|
|
9214
|
+
to { transform: rotate(360deg); }
|
|
9215
|
+
}
|
|
9216
|
+
` })] }));
|
|
9217
|
+
}
|
|
9114
9218
|
const spinner$1 = (borderColor) => (jsx("div", { style: {
|
|
9115
9219
|
width: "auto",
|
|
9116
9220
|
height: "auto",
|
|
@@ -9127,7 +9231,7 @@ const spinner$1 = (borderColor) => (jsx("div", { style: {
|
|
|
9127
9231
|
borderRadius: "50%",
|
|
9128
9232
|
} }) }));
|
|
9129
9233
|
// Inner component that uses the Stripe hooks
|
|
9130
|
-
function PaymentFormInner({ config, eventDetails, formData, totalAmount, discountCode, onSuccess, onError, }) {
|
|
9234
|
+
function PaymentFormInner({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, }) {
|
|
9131
9235
|
const stripe = reactStripe_umdExports.useStripe();
|
|
9132
9236
|
const elements = reactStripe_umdExports.useElements();
|
|
9133
9237
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -9258,7 +9362,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, discoun
|
|
|
9258
9362
|
` })] }));
|
|
9259
9363
|
}
|
|
9260
9364
|
// Main PaymentForm component that handles payment intent creation and Elements wrapper
|
|
9261
|
-
function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, }) {
|
|
9365
|
+
function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, }) {
|
|
9262
9366
|
const [clientSecret, setClientSecret] = useState(null);
|
|
9263
9367
|
const [paymentIntentId, setPaymentIntentId] = useState(null);
|
|
9264
9368
|
const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = useState(false);
|
|
@@ -9354,6 +9458,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
9354
9458
|
currency: "eur",
|
|
9355
9459
|
participants: formData.participants.filter((p) => p.name?.trim()),
|
|
9356
9460
|
discountCode: discountCode?.code,
|
|
9461
|
+
giftCardCodes: giftCards?.map((gc) => gc.code) || [],
|
|
9357
9462
|
customerName: formData.customerName?.trim(),
|
|
9358
9463
|
customerEmail: formData.customerEmail?.trim(),
|
|
9359
9464
|
customerPhone: formData.customerPhone?.trim(),
|
|
@@ -9415,8 +9520,21 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
9415
9520
|
formData.customerName,
|
|
9416
9521
|
totalAmount,
|
|
9417
9522
|
discountCode,
|
|
9523
|
+
giftCards,
|
|
9418
9524
|
config,
|
|
9419
9525
|
]);
|
|
9526
|
+
// Calculate total gift card coverage
|
|
9527
|
+
const totalGiftCardAmount = giftCards?.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0) || 0;
|
|
9528
|
+
const baseTotal = eventDetails?.price
|
|
9529
|
+
? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
|
|
9530
|
+
: 0;
|
|
9531
|
+
const discountAmount = discountCode?.discountAmount || 0;
|
|
9532
|
+
const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
|
|
9533
|
+
const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
|
|
9534
|
+
// If gift cards fully cover the payment, show a simplified booking button
|
|
9535
|
+
if (isFullyCoveredByGiftCards && totalAmount <= 0) {
|
|
9536
|
+
return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards || [], onSuccess: onSuccess, onError: onError }));
|
|
9537
|
+
}
|
|
9420
9538
|
// Show loading state while creating payment intent
|
|
9421
9539
|
if (isCreatingPaymentIntent || !clientSecret) {
|
|
9422
9540
|
return (jsxs("div", { style: {
|
|
@@ -9450,7 +9568,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
|
|
|
9450
9568
|
clientSecret,
|
|
9451
9569
|
appearance: stripeAppearance || { theme: "stripe" },
|
|
9452
9570
|
locale: config.locale || "de",
|
|
9453
|
-
}, children: jsx(PaymentFormInner, { config: config, eventDetails: eventDetails, formData: formData, totalAmount: totalAmount, discountCode: discountCode, onSuccess: (result) => {
|
|
9571
|
+
}, children: jsx(PaymentFormInner, { config: config, eventDetails: eventDetails, formData: formData, totalAmount: totalAmount, discountCode: discountCode, giftCards: giftCards, onSuccess: (result) => {
|
|
9454
9572
|
// Clear persisted PI data on successful payment
|
|
9455
9573
|
clearPersistedPaymentIntent();
|
|
9456
9574
|
setPaymentIntentId(null);
|
|
@@ -9655,6 +9773,222 @@ function Accordion({ title, priceInfo, children, isOpen, onToggle }) {
|
|
|
9655
9773
|
}, children: children }))] }));
|
|
9656
9774
|
}
|
|
9657
9775
|
|
|
9776
|
+
// Icons
|
|
9777
|
+
const IconTicket = ({ size = 20, color = "currentColor" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("path", { d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z" }), jsx("path", { d: "M13 5v2" }), jsx("path", { d: "M13 17v2" }), jsx("path", { d: "M13 11v2" })] }));
|
|
9778
|
+
const IconGift = ({ size = 20, color = "currentColor" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("rect", { x: "3", y: "8", width: "18", height: "4", rx: "1" }), jsx("path", { d: "M12 8v13" }), jsx("path", { d: "M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7" }), jsx("path", { d: "M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5" })] }));
|
|
9779
|
+
const IconCheck = ({ size = 16, color = "currentColor" }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsx("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
9780
|
+
const IconX = ({ size = 16, color = "currentColor" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
|
|
9781
|
+
const IconSpinner = ({ size = 16, color = "currentColor" }) => (jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
|
|
9782
|
+
function VoucherInput({ config, orderValue, eventInstanceId, customerEmail, onVoucherValidated, appliedVouchers, onRemoveVoucher, disabled = false, }) {
|
|
9783
|
+
const [inputValue, setInputValue] = useState("");
|
|
9784
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
9785
|
+
const [error, setError] = useState(null);
|
|
9786
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
9787
|
+
// Check if a discount code is already applied (only one allowed)
|
|
9788
|
+
const hasDiscountCode = appliedVouchers.some((v) => v.type === "discount");
|
|
9789
|
+
const validateVoucher = useCallback(async (code) => {
|
|
9790
|
+
if (!code.trim()) {
|
|
9791
|
+
setError(null);
|
|
9792
|
+
return;
|
|
9793
|
+
}
|
|
9794
|
+
// Check if code is already applied
|
|
9795
|
+
if (appliedVouchers.some((v) => v.code.toUpperCase() === code.toUpperCase())) {
|
|
9796
|
+
setError("Dieser Code wurde bereits angewendet");
|
|
9797
|
+
return;
|
|
9798
|
+
}
|
|
9799
|
+
setIsLoading(true);
|
|
9800
|
+
setError(null);
|
|
9801
|
+
try {
|
|
9802
|
+
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/validate-voucher"), {
|
|
9803
|
+
method: "POST",
|
|
9804
|
+
headers: createApiHeaders(config),
|
|
9805
|
+
body: JSON.stringify(createRequestBody(config, {
|
|
9806
|
+
code: code.trim(),
|
|
9807
|
+
orderValue: orderValue,
|
|
9808
|
+
eventInstanceId: eventInstanceId,
|
|
9809
|
+
customerEmail: customerEmail,
|
|
9810
|
+
})),
|
|
9811
|
+
});
|
|
9812
|
+
const data = await response.json();
|
|
9813
|
+
if (data.valid && data.voucher) {
|
|
9814
|
+
// Check if trying to add a second discount code
|
|
9815
|
+
if (data.voucher.type === "discount" && hasDiscountCode) {
|
|
9816
|
+
setError("Es kann nur ein Rabattcode verwendet werden");
|
|
9817
|
+
onVoucherValidated(null, "Es kann nur ein Rabattcode verwendet werden");
|
|
9818
|
+
return;
|
|
9819
|
+
}
|
|
9820
|
+
onVoucherValidated(data.voucher);
|
|
9821
|
+
setInputValue("");
|
|
9822
|
+
setError(null);
|
|
9823
|
+
}
|
|
9824
|
+
else {
|
|
9825
|
+
setError(data.error || "Code nicht gefunden oder ungültig");
|
|
9826
|
+
onVoucherValidated(null, data.error);
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
catch (err) {
|
|
9830
|
+
const errorMsg = "Fehler beim Validieren des Codes";
|
|
9831
|
+
setError(errorMsg);
|
|
9832
|
+
onVoucherValidated(null, errorMsg);
|
|
9833
|
+
}
|
|
9834
|
+
finally {
|
|
9835
|
+
setIsLoading(false);
|
|
9836
|
+
}
|
|
9837
|
+
}, [
|
|
9838
|
+
config,
|
|
9839
|
+
orderValue,
|
|
9840
|
+
eventInstanceId,
|
|
9841
|
+
customerEmail,
|
|
9842
|
+
appliedVouchers,
|
|
9843
|
+
hasDiscountCode,
|
|
9844
|
+
onVoucherValidated,
|
|
9845
|
+
]);
|
|
9846
|
+
const handleSubmit = () => {
|
|
9847
|
+
if (inputValue.trim() && !isLoading && !disabled) {
|
|
9848
|
+
validateVoucher(inputValue);
|
|
9849
|
+
}
|
|
9850
|
+
};
|
|
9851
|
+
const handleKeyDown = (e) => {
|
|
9852
|
+
if (e.key === "Enter") {
|
|
9853
|
+
e.preventDefault();
|
|
9854
|
+
if (inputValue.trim() && !isLoading && !disabled) {
|
|
9855
|
+
validateVoucher(inputValue);
|
|
9856
|
+
}
|
|
9857
|
+
}
|
|
9858
|
+
};
|
|
9859
|
+
const inputStyle = {
|
|
9860
|
+
flex: 1,
|
|
9861
|
+
padding: "10px 12px",
|
|
9862
|
+
backgroundColor: "var(--bw-background-color)",
|
|
9863
|
+
border: "1px solid var(--bw-border-color)",
|
|
9864
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9865
|
+
color: "var(--bw-text-color)",
|
|
9866
|
+
fontSize: "var(--bw-font-size)",
|
|
9867
|
+
fontFamily: "var(--bw-font-family)",
|
|
9868
|
+
outline: "none",
|
|
9869
|
+
transition: "all 0.2s ease",
|
|
9870
|
+
textTransform: "uppercase",
|
|
9871
|
+
};
|
|
9872
|
+
const buttonStyle = {
|
|
9873
|
+
padding: "10px 16px",
|
|
9874
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
9875
|
+
border: "none",
|
|
9876
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9877
|
+
color: "#fff",
|
|
9878
|
+
fontSize: "var(--bw-font-size)",
|
|
9879
|
+
fontFamily: "var(--bw-font-family)",
|
|
9880
|
+
fontWeight: "600",
|
|
9881
|
+
cursor: disabled || isLoading ? "not-allowed" : "pointer",
|
|
9882
|
+
opacity: disabled || isLoading ? 0.6 : 1,
|
|
9883
|
+
transition: "all 0.2s ease",
|
|
9884
|
+
display: "flex",
|
|
9885
|
+
alignItems: "center",
|
|
9886
|
+
justifyContent: "center",
|
|
9887
|
+
gap: "6px",
|
|
9888
|
+
minWidth: "100px",
|
|
9889
|
+
};
|
|
9890
|
+
const appliedVoucherStyle = {
|
|
9891
|
+
display: "flex",
|
|
9892
|
+
alignItems: "center",
|
|
9893
|
+
justifyContent: "space-between",
|
|
9894
|
+
padding: "10px 12px",
|
|
9895
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
9896
|
+
border: "1px solid var(--bw-border-color)",
|
|
9897
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9898
|
+
marginBottom: "8px",
|
|
9899
|
+
};
|
|
9900
|
+
const removeButtonStyle = {
|
|
9901
|
+
background: "none",
|
|
9902
|
+
border: "none",
|
|
9903
|
+
padding: "4px",
|
|
9904
|
+
cursor: "pointer",
|
|
9905
|
+
color: "var(--bw-error-color)",
|
|
9906
|
+
display: "flex",
|
|
9907
|
+
alignItems: "center",
|
|
9908
|
+
justifyContent: "center",
|
|
9909
|
+
borderRadius: "50%",
|
|
9910
|
+
transition: "background-color 0.2s ease",
|
|
9911
|
+
};
|
|
9912
|
+
return (jsxs("div", { style: {
|
|
9913
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
9914
|
+
border: "1px solid var(--bw-border-color)",
|
|
9915
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9916
|
+
overflow: "hidden",
|
|
9917
|
+
}, children: [jsxs("button", { type: "button", onClick: () => setIsExpanded(!isExpanded), style: {
|
|
9918
|
+
width: "100%",
|
|
9919
|
+
padding: "var(--bw-spacing)",
|
|
9920
|
+
backgroundColor: "transparent",
|
|
9921
|
+
border: "none",
|
|
9922
|
+
cursor: "pointer",
|
|
9923
|
+
display: "flex",
|
|
9924
|
+
alignItems: "center",
|
|
9925
|
+
justifyContent: "space-between",
|
|
9926
|
+
color: "var(--bw-text-color)",
|
|
9927
|
+
fontFamily: "var(--bw-font-family)",
|
|
9928
|
+
fontSize: "var(--bw-font-size)",
|
|
9929
|
+
fontWeight: "500",
|
|
9930
|
+
}, children: [jsxs("span", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [jsx(IconTicket, { size: 18, color: "var(--bw-highlight-color)" }), "Rabattcode oder Gutschein", appliedVouchers.length > 0 && (jsx("span", { style: {
|
|
9931
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
9932
|
+
color: "#fff",
|
|
9933
|
+
padding: "2px 8px",
|
|
9934
|
+
borderRadius: "12px",
|
|
9935
|
+
fontSize: "12px",
|
|
9936
|
+
fontWeight: "600",
|
|
9937
|
+
}, children: appliedVouchers.length }))] }), jsx("span", { style: {
|
|
9938
|
+
transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
|
|
9939
|
+
transition: "transform 0.2s ease",
|
|
9940
|
+
}, children: "\u25BC" })] }), isExpanded && (jsxs("div", { style: { padding: "0 var(--bw-spacing) var(--bw-spacing)" }, children: [appliedVouchers.length > 0 && (jsx("div", { style: { marginBottom: "12px" }, children: appliedVouchers.map((voucher) => (jsxs("div", { style: appliedVoucherStyle, children: [jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [voucher.type === "discount" ? (jsx(IconTicket, { size: 18, color: "var(--bw-success-color)" })) : (jsx(IconGift, { size: 18, color: "var(--bw-success-color)" })), jsxs("div", { children: [jsxs("div", { style: {
|
|
9941
|
+
fontFamily: "var(--bw-font-family)",
|
|
9942
|
+
fontSize: "var(--bw-font-size)",
|
|
9943
|
+
fontWeight: "600",
|
|
9944
|
+
color: "var(--bw-text-color)",
|
|
9945
|
+
display: "flex",
|
|
9946
|
+
alignItems: "center",
|
|
9947
|
+
gap: "6px",
|
|
9948
|
+
}, children: [jsx("span", { style: { fontFamily: "monospace" }, children: voucher.code }), jsx(IconCheck, { size: 14, color: "var(--bw-success-color)" })] }), jsxs("div", { style: {
|
|
9949
|
+
fontFamily: "var(--bw-font-family)",
|
|
9950
|
+
fontSize: "12px",
|
|
9951
|
+
color: "var(--bw-success-color)",
|
|
9952
|
+
}, children: [voucher.type === "discount"
|
|
9953
|
+
? `−${formatCurrency(voucher.discountAmount)} Rabatt`
|
|
9954
|
+
: `−${formatCurrency(voucher.balanceToUse || voucher.discountAmount)} Gutschein`, voucher.type === "giftCard" &&
|
|
9955
|
+
voucher.remainingBalance !== undefined &&
|
|
9956
|
+
voucher.remainingBalance > 0 && (jsxs("span", { style: { color: "var(--bw-text-muted)", marginLeft: "8px" }, children: ["(Rest: ", formatCurrency(voucher.remainingBalance), ")"] }))] })] })] }), jsx("button", { type: "button", onClick: () => onRemoveVoucher(voucher.code), style: removeButtonStyle, title: "Entfernen", children: jsx(IconX, { size: 16 }) })] }, voucher.code))) })), jsxs("div", { style: { display: "flex", gap: "8px" }, children: [jsx("input", { type: "text", value: inputValue, onChange: (e) => {
|
|
9957
|
+
setInputValue(e.target.value.toUpperCase());
|
|
9958
|
+
setError(null);
|
|
9959
|
+
}, onKeyDown: handleKeyDown, placeholder: hasDiscountCode
|
|
9960
|
+
? "Gutscheincode eingeben..."
|
|
9961
|
+
: "Rabatt- oder Gutscheincode eingeben...", style: inputStyle, disabled: disabled || isLoading, onFocus: (e) => {
|
|
9962
|
+
e.target.style.borderColor = "var(--bw-highlight-color)";
|
|
9963
|
+
e.target.style.boxShadow = "0 0 0 2px var(--bw-highlight-color)33";
|
|
9964
|
+
}, onBlur: (e) => {
|
|
9965
|
+
e.target.style.borderColor = "var(--bw-border-color)";
|
|
9966
|
+
e.target.style.boxShadow = "none";
|
|
9967
|
+
} }), jsx("button", { type: "button", onClick: handleSubmit, style: buttonStyle, disabled: disabled || isLoading || !inputValue.trim(), children: isLoading ? (jsx(IconSpinner, { size: 16, color: "#fff" })) : (jsxs(Fragment, { children: [jsx(IconCheck, { size: 16 }), "Einl\u00F6sen"] })) })] }), error && (jsxs("div", { style: {
|
|
9968
|
+
marginTop: "8px",
|
|
9969
|
+
padding: "8px 12px",
|
|
9970
|
+
backgroundColor: "var(--bw-error-color)15",
|
|
9971
|
+
border: "1px solid var(--bw-error-color)40",
|
|
9972
|
+
borderRadius: "var(--bw-border-radius)",
|
|
9973
|
+
color: "var(--bw-error-color)",
|
|
9974
|
+
fontSize: "var(--bw-font-size)",
|
|
9975
|
+
fontFamily: "var(--bw-font-family)",
|
|
9976
|
+
display: "flex",
|
|
9977
|
+
alignItems: "center",
|
|
9978
|
+
gap: "8px",
|
|
9979
|
+
}, children: [jsx(IconX, { size: 16 }), error] })), hasDiscountCode && (jsx("div", { style: {
|
|
9980
|
+
marginTop: "8px",
|
|
9981
|
+
fontSize: "12px",
|
|
9982
|
+
color: "var(--bw-text-muted)",
|
|
9983
|
+
fontFamily: "var(--bw-font-family)",
|
|
9984
|
+
}, children: "\uD83D\uDCA1 Es wurde bereits ein Rabattcode angewendet. Du kannst weitere Gutscheine hinzuf\u00FCgen." }))] })), jsx("style", { children: `
|
|
9985
|
+
@keyframes spin {
|
|
9986
|
+
from { transform: rotate(0deg); }
|
|
9987
|
+
to { transform: rotate(360deg); }
|
|
9988
|
+
}
|
|
9989
|
+
` })] }));
|
|
9990
|
+
}
|
|
9991
|
+
|
|
9658
9992
|
// Form schemas
|
|
9659
9993
|
const participantSchema = objectType({
|
|
9660
9994
|
name: stringType().min(1, "Name ist erforderlich"),
|
|
@@ -9674,6 +10008,10 @@ const bookingFormSchema = objectType({
|
|
|
9674
10008
|
const IconWarning = ({ size = 48, color = "var(--bw-error-color)" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("circle", { cx: "12", cy: "12", r: "10" }), jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), jsx("circle", { cx: "12", cy: "16", r: "1" })] }));
|
|
9675
10009
|
const IconMoney = ({ size = 20, color = "var(--bw-text-muted)" }) => (jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsx("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }), jsx("circle", { cx: "12", cy: "12", r: "4" }), jsx("line", { x1: "2", y1: "10", x2: "2", y2: "14" }), jsx("line", { x1: "22", y1: "10", x2: "22", y2: "14" })] }));
|
|
9676
10010
|
function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError, onBackToEventInstances, onBackToEventTypes, selectedEventType, selectedEventInstance, isOpen, onClose, systemConfig, }) {
|
|
10011
|
+
// New voucher system - supports multiple gift cards + one discount code
|
|
10012
|
+
const [appliedVouchers, setAppliedVouchers] = useState([]);
|
|
10013
|
+
const [voucherError, setVoucherError] = useState(null);
|
|
10014
|
+
// Legacy state for backward compatibility
|
|
9677
10015
|
const [discountCode, setDiscountCode] = useState(null);
|
|
9678
10016
|
const [discountLoading, setDiscountLoading] = useState(false);
|
|
9679
10017
|
const [discountError, setDiscountError] = useState(null);
|
|
@@ -9687,28 +10025,66 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
9687
10025
|
},
|
|
9688
10026
|
});
|
|
9689
10027
|
const watchedParticipants = form.watch("participants");
|
|
9690
|
-
|
|
10028
|
+
form.watch("discountCode");
|
|
9691
10029
|
const watchedCustomerName = form.watch("customerName");
|
|
9692
10030
|
const watchedCustomerEmail = form.watch("customerEmail");
|
|
9693
10031
|
const customerNameError = form.formState.errors.customerName;
|
|
9694
10032
|
const customerEmailError = form.formState.errors.customerEmail;
|
|
9695
10033
|
const watchedAcceptTerms = form.watch("acceptTerms");
|
|
9696
|
-
// Calculate total
|
|
9697
|
-
const
|
|
10034
|
+
// Calculate base total before any discounts
|
|
10035
|
+
const calculateBaseTotal = useCallback(() => {
|
|
9698
10036
|
if (!eventDetails)
|
|
9699
10037
|
return 0;
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
10038
|
+
return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
|
|
10039
|
+
}, [eventDetails, watchedParticipants]);
|
|
10040
|
+
// Calculate total discount from all applied vouchers
|
|
10041
|
+
const calculateTotalDiscount = useCallback(() => {
|
|
10042
|
+
return appliedVouchers.reduce((total, voucher) => {
|
|
10043
|
+
if (voucher.type === "discount") {
|
|
10044
|
+
return total + voucher.discountAmount;
|
|
10045
|
+
}
|
|
10046
|
+
else if (voucher.type === "giftCard") {
|
|
10047
|
+
return total + (voucher.balanceToUse || voucher.discountAmount);
|
|
10048
|
+
}
|
|
10049
|
+
return total;
|
|
10050
|
+
}, 0);
|
|
10051
|
+
}, [appliedVouchers]);
|
|
10052
|
+
// Calculate total amount after discounts
|
|
10053
|
+
const calculateTotal = useCallback(() => {
|
|
10054
|
+
const baseTotal = calculateBaseTotal();
|
|
10055
|
+
const totalDiscount = calculateTotalDiscount();
|
|
10056
|
+
return Math.max(0, baseTotal - totalDiscount);
|
|
10057
|
+
}, [calculateBaseTotal, calculateTotalDiscount]);
|
|
9703
10058
|
const calculateDeposit = () => {
|
|
9704
10059
|
if (!eventDetails || !eventDetails.deposit)
|
|
9705
10060
|
return 0;
|
|
9706
10061
|
const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
|
|
9707
10062
|
return eventDetails.deposit * participantCount;
|
|
9708
10063
|
};
|
|
10064
|
+
const baseTotal = calculateBaseTotal();
|
|
10065
|
+
const totalDiscount = calculateTotalDiscount();
|
|
9709
10066
|
const totalAmount = calculateTotal();
|
|
9710
10067
|
const depositAmount = calculateDeposit();
|
|
9711
|
-
|
|
10068
|
+
// If there's a deposit, we pay the deposit; otherwise we pay the total after discounts
|
|
10069
|
+
const paymentAmount = depositAmount > 0 ? Math.max(0, depositAmount - totalDiscount) : totalAmount;
|
|
10070
|
+
// Get discount code for legacy compatibility
|
|
10071
|
+
const appliedDiscountCode = appliedVouchers.find((v) => v.type === "discount");
|
|
10072
|
+
// Get gift cards
|
|
10073
|
+
const appliedGiftCards = appliedVouchers.filter((v) => v.type === "giftCard");
|
|
10074
|
+
// Voucher handlers
|
|
10075
|
+
const handleVoucherValidated = useCallback((voucher, error) => {
|
|
10076
|
+
if (error) {
|
|
10077
|
+
setVoucherError(error);
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
10080
|
+
if (voucher) {
|
|
10081
|
+
setAppliedVouchers((prev) => [...prev, voucher]);
|
|
10082
|
+
setVoucherError(null);
|
|
10083
|
+
}
|
|
10084
|
+
}, []);
|
|
10085
|
+
const handleRemoveVoucher = useCallback((code) => {
|
|
10086
|
+
setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
|
|
10087
|
+
}, []);
|
|
9712
10088
|
// Form validation helper
|
|
9713
10089
|
const isFormValid = () => {
|
|
9714
10090
|
const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
|
|
@@ -9722,48 +10098,51 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
9722
10098
|
const hasAcceptedTerms = watchedAcceptTerms === true;
|
|
9723
10099
|
return validParticipants && participantsWithinLimit && hasName && hasEmail && hasAcceptedTerms;
|
|
9724
10100
|
};
|
|
9725
|
-
//
|
|
10101
|
+
// Re-validate vouchers when participant count changes (affects order value)
|
|
9726
10102
|
useEffect(() => {
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
10103
|
+
// When participants change, we need to recalculate voucher amounts
|
|
10104
|
+
// For now, we'll clear vouchers if the order value changes significantly
|
|
10105
|
+
// In a production app, you might want to re-validate each voucher
|
|
10106
|
+
if (appliedVouchers.length > 0) {
|
|
10107
|
+
// Recalculate discount amounts based on new order value
|
|
10108
|
+
const newBaseTotal = eventDetails?.price
|
|
10109
|
+
? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
|
|
10110
|
+
: 0;
|
|
10111
|
+
// Update voucher amounts (simplified - in production, re-validate via API)
|
|
10112
|
+
setAppliedVouchers((prev) => prev.map((voucher) => {
|
|
10113
|
+
if (voucher.type === "discount") {
|
|
10114
|
+
let newDiscountAmount = 0;
|
|
10115
|
+
if (voucher.discountType === "percentage") {
|
|
10116
|
+
newDiscountAmount = Math.round((newBaseTotal * (voucher.discountValue || 0)) / 10000);
|
|
10117
|
+
}
|
|
10118
|
+
else {
|
|
10119
|
+
newDiscountAmount = voucher.discountValue || 0;
|
|
10120
|
+
}
|
|
10121
|
+
newDiscountAmount = Math.min(newDiscountAmount, newBaseTotal);
|
|
10122
|
+
return {
|
|
10123
|
+
...voucher,
|
|
10124
|
+
discountAmount: newDiscountAmount,
|
|
10125
|
+
newTotal: newBaseTotal - newDiscountAmount,
|
|
10126
|
+
};
|
|
9748
10127
|
}
|
|
9749
|
-
else {
|
|
9750
|
-
|
|
9751
|
-
|
|
10128
|
+
else if (voucher.type === "giftCard") {
|
|
10129
|
+
// Gift card balance stays the same, but amount to use might change
|
|
10130
|
+
const remainingAfterDiscount = newBaseTotal -
|
|
10131
|
+
prev
|
|
10132
|
+
.filter((v) => v.type === "discount")
|
|
10133
|
+
.reduce((sum, v) => sum + v.discountAmount, 0);
|
|
10134
|
+
const balanceToUse = Math.min(voucher.currentBalance || 0, Math.max(0, remainingAfterDiscount));
|
|
10135
|
+
return {
|
|
10136
|
+
...voucher,
|
|
10137
|
+
balanceToUse,
|
|
10138
|
+
remainingBalance: (voucher.currentBalance || 0) - balanceToUse,
|
|
10139
|
+
discountAmount: balanceToUse,
|
|
10140
|
+
};
|
|
9752
10141
|
}
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
}
|
|
9758
|
-
finally {
|
|
9759
|
-
setDiscountLoading(false);
|
|
9760
|
-
}
|
|
9761
|
-
};
|
|
9762
|
-
const timer = setTimeout(() => {
|
|
9763
|
-
validateDiscountCode(watchedDiscountCode || "");
|
|
9764
|
-
}, 500);
|
|
9765
|
-
return () => clearTimeout(timer);
|
|
9766
|
-
}, [watchedDiscountCode, watchedParticipants, eventDetails, config]);
|
|
10142
|
+
return voucher;
|
|
10143
|
+
}));
|
|
10144
|
+
}
|
|
10145
|
+
}, [watchedParticipants, eventDetails]);
|
|
9767
10146
|
// Helper functions
|
|
9768
10147
|
const addParticipant = () => {
|
|
9769
10148
|
const currentParticipants = form.getValues("participants");
|
|
@@ -9918,7 +10297,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
9918
10297
|
color: "var(--bw-text-color)",
|
|
9919
10298
|
fontWeight: "500",
|
|
9920
10299
|
fontFamily: "var(--bw-font-family)",
|
|
9921
|
-
}, children: [formatCurrency(eventDetails.price), " pro Person"] })] })] })] }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxs("
|
|
10300
|
+
}, children: [formatCurrency(eventDetails.price), " pro Person"] })] })] })] }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxs("div", { style: {
|
|
9922
10301
|
backgroundColor: "var(--bw-surface-color)",
|
|
9923
10302
|
border: `1px solid var(--bw-border-color)`,
|
|
9924
10303
|
backdropFilter: "blur(4px)",
|
|
@@ -10063,7 +10442,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
10063
10442
|
color: "var(--bw-error-color)",
|
|
10064
10443
|
fontSize: "var(--bw-font-size)",
|
|
10065
10444
|
fontFamily: "var(--bw-font-family)",
|
|
10066
|
-
}, children: ["Maximal ", eventDetails.availableSpots, " Pl\u00E4tze verf\u00FCgbar."] }))] })] }), jsxs("div", { style: {
|
|
10445
|
+
}, children: ["Maximal ", eventDetails.availableSpots, " Pl\u00E4tze verf\u00FCgbar."] }))] })] }), jsx(VoucherInput, { config: config, orderValue: baseTotal, eventInstanceId: eventDetails?.id, customerEmail: watchedCustomerEmail, onVoucherValidated: handleVoucherValidated, appliedVouchers: appliedVouchers, onRemoveVoucher: handleRemoveVoucher, disabled: !eventDetails }), jsxs("div", { style: {
|
|
10067
10446
|
backgroundColor: "var(--bw-surface-color)",
|
|
10068
10447
|
border: `1px solid var(--bw-border-color)`,
|
|
10069
10448
|
backdropFilter: "blur(4px)",
|
|
@@ -10162,7 +10541,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
10162
10541
|
color: "var(--bw-text-color)",
|
|
10163
10542
|
fontWeight: "500",
|
|
10164
10543
|
fontFamily: "var(--bw-font-family)",
|
|
10165
|
-
}, children: formatCurrency(eventDetails.deposit || 0) })] })),
|
|
10544
|
+
}, children: formatCurrency(eventDetails.deposit || 0) })] })), appliedVouchers.length > 0 && (jsxs(Fragment, { children: [jsxs("div", { style: {
|
|
10166
10545
|
display: "flex",
|
|
10167
10546
|
justifyContent: "space-between",
|
|
10168
10547
|
alignItems: "center",
|
|
@@ -10171,20 +10550,31 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
10171
10550
|
fontFamily: "var(--bw-font-family)",
|
|
10172
10551
|
}, children: "Zwischensumme:" }), jsx("span", { style: {
|
|
10173
10552
|
color: "var(--bw-text-muted)",
|
|
10174
|
-
textDecoration: "line-through",
|
|
10553
|
+
textDecoration: totalDiscount > 0 ? "line-through" : "none",
|
|
10175
10554
|
fontFamily: "var(--bw-font-family)",
|
|
10176
|
-
}, children: formatCurrency(
|
|
10177
|
-
watchedParticipants.filter((p) => p.name.trim()).length) })] }), jsxs("div", { style: {
|
|
10555
|
+
}, children: formatCurrency(baseTotal) })] }), appliedDiscountCode && (jsxs("div", { style: {
|
|
10178
10556
|
display: "flex",
|
|
10179
10557
|
justifyContent: "space-between",
|
|
10180
10558
|
alignItems: "center",
|
|
10181
|
-
}, children: [
|
|
10559
|
+
}, children: [jsxs("span", { style: {
|
|
10182
10560
|
color: "var(--bw-success-color)",
|
|
10183
10561
|
fontFamily: "var(--bw-font-family)",
|
|
10184
|
-
|
|
10562
|
+
fontSize: "var(--bw-font-size)",
|
|
10563
|
+
}, children: ["Rabatt (", appliedDiscountCode.code, "):"] }), jsxs("span", { style: {
|
|
10185
10564
|
color: "var(--bw-success-color)",
|
|
10186
10565
|
fontFamily: "var(--bw-font-family)",
|
|
10187
|
-
}, children: ["-", formatCurrency(
|
|
10566
|
+
}, children: ["-", formatCurrency(appliedDiscountCode.discountAmount)] })] })), appliedGiftCards.map((giftCard) => (jsxs("div", { style: {
|
|
10567
|
+
display: "flex",
|
|
10568
|
+
justifyContent: "space-between",
|
|
10569
|
+
alignItems: "center",
|
|
10570
|
+
}, children: [jsxs("span", { style: {
|
|
10571
|
+
color: "var(--bw-success-color)",
|
|
10572
|
+
fontFamily: "var(--bw-font-family)",
|
|
10573
|
+
fontSize: "var(--bw-font-size)",
|
|
10574
|
+
}, children: ["Gutschein (", giftCard.code, "):"] }), jsxs("span", { style: {
|
|
10575
|
+
color: "var(--bw-success-color)",
|
|
10576
|
+
fontFamily: "var(--bw-font-family)",
|
|
10577
|
+
}, children: ["-", formatCurrency(giftCard.balanceToUse || giftCard.discountAmount)] })] }, giftCard.code)))] })), jsxs("div", { style: {
|
|
10188
10578
|
borderTop: `1px solid var(--bw-border-color)`,
|
|
10189
10579
|
paddingTop: "12px",
|
|
10190
10580
|
}, children: [depositAmount > 0 && (jsxs("div", { style: {
|
|
@@ -10257,7 +10647,15 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
10257
10647
|
fontFamily: "var(--bw-font-family)",
|
|
10258
10648
|
borderBottom: "2px solid var(--bw-highlight-color)",
|
|
10259
10649
|
paddingBottom: 4,
|
|
10260
|
-
}, children: "Zahlung" }), jsx(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode:
|
|
10650
|
+
}, children: "Zahlung" }), jsx(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: appliedDiscountCode ? {
|
|
10651
|
+
id: appliedDiscountCode.id,
|
|
10652
|
+
code: appliedDiscountCode.code,
|
|
10653
|
+
description: appliedDiscountCode.description || undefined,
|
|
10654
|
+
type: appliedDiscountCode.discountType || "percentage",
|
|
10655
|
+
value: appliedDiscountCode.discountValue || 0,
|
|
10656
|
+
discountAmount: appliedDiscountCode.discountAmount,
|
|
10657
|
+
newTotal: appliedDiscountCode.newTotal,
|
|
10658
|
+
} : null, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, stripePromise: stripePromise, stripeAppearance: stripeAppearance })] }));
|
|
10261
10659
|
})()] })] }), jsx("style", { children: `
|
|
10262
10660
|
.booking-widget-container *,
|
|
10263
10661
|
.booking-widget-container *::before,
|
|
@@ -11284,137 +11682,155 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
|
|
|
11284
11682
|
font-size: 1.1rem !important;
|
|
11285
11683
|
}
|
|
11286
11684
|
}
|
|
11287
|
-
` }),
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
backgroundColor:
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
:
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
}, children:
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11685
|
+
` }), jsx(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, children: jsx("div", { className: "bw-event-instance-list", style: { padding: "24px" }, children: jsx("div", { style: {
|
|
11686
|
+
display: "flex",
|
|
11687
|
+
flexDirection: "column",
|
|
11688
|
+
gap: "20px",
|
|
11689
|
+
}, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
|
|
11690
|
+
const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
|
|
11691
|
+
return (jsxs(Fragment$1, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsx("div", { style: {
|
|
11692
|
+
height: 1,
|
|
11693
|
+
backgroundColor: "var(--bw-border-color)",
|
|
11694
|
+
margin: "4px 0",
|
|
11695
|
+
} })), jsx(Accordion, { title: label, priceInfo: jsx("div", { style: {
|
|
11696
|
+
fontSize: "1rem",
|
|
11697
|
+
backgroundColor: monthPriceDisplayInfo
|
|
11698
|
+
? monthPriceDisplayInfo.backgroundColor
|
|
11699
|
+
: "#14532d",
|
|
11700
|
+
color: monthPriceDisplayInfo
|
|
11701
|
+
? monthPriceDisplayInfo.textColor
|
|
11702
|
+
: "#4ade80",
|
|
11703
|
+
fontWeight: 500,
|
|
11704
|
+
marginLeft: "auto",
|
|
11705
|
+
padding: "4px 8px",
|
|
11706
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
11707
|
+
border: monthPriceDisplayInfo ? "none" : undefined,
|
|
11708
|
+
boxShadow: monthPriceDisplayInfo
|
|
11709
|
+
? "0 2px 4px rgba(0, 0, 0, 0.2)"
|
|
11710
|
+
: undefined,
|
|
11711
|
+
}, children: `ab ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: {
|
|
11712
|
+
display: "flex",
|
|
11713
|
+
flexDirection: "column",
|
|
11714
|
+
gap: "12px",
|
|
11715
|
+
paddingTop: "12px",
|
|
11716
|
+
}, children: events.map((event) => {
|
|
11717
|
+
const availableSpots = event.maxParticipants - event.participantCount;
|
|
11718
|
+
const isFullyBooked = availableSpots === 0;
|
|
11719
|
+
const startDate = new Date(event.startTime);
|
|
11720
|
+
const isPastEvent = today.toISOString() >= startDate.toISOString();
|
|
11721
|
+
return (jsxs("div", { className: "bw-event-instance-card", style: {
|
|
11722
|
+
position: "relative",
|
|
11723
|
+
cursor: !isFullyBooked && !isPastEvent && event.bookingOpen
|
|
11724
|
+
? "pointer"
|
|
11725
|
+
: "not-allowed",
|
|
11726
|
+
border: "1px solid var(--bw-border-color)",
|
|
11727
|
+
backgroundColor: "var(--bw-surface-color)",
|
|
11728
|
+
borderRadius: "var(--bw-border-radius)",
|
|
11729
|
+
padding: "16px 20px",
|
|
11730
|
+
transition: "all 0.2s ease",
|
|
11731
|
+
opacity: isFullyBooked || isPastEvent ? 0.3 : 1,
|
|
11732
|
+
filter: isFullyBooked || isPastEvent ? "grayscale(40%)" : "none",
|
|
11733
|
+
fontFamily: "var(--bw-font-family)",
|
|
11734
|
+
}, onClick: () => {
|
|
11735
|
+
if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
|
|
11736
|
+
handleEventInstanceSelect(event);
|
|
11737
|
+
}
|
|
11738
|
+
}, onMouseEnter: (e) => {
|
|
11739
|
+
if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
|
|
11740
|
+
e.currentTarget.style.transform = "scale(1.02)";
|
|
11741
|
+
e.currentTarget.style.backgroundColor =
|
|
11742
|
+
"var(--bw-surface-muted, rgba(59, 130, 246, 0.1))";
|
|
11743
|
+
}
|
|
11744
|
+
}, onMouseLeave: (e) => {
|
|
11745
|
+
if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
|
|
11746
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
11747
|
+
e.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
|
|
11748
|
+
}
|
|
11749
|
+
}, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
|
|
11750
|
+
position: "absolute",
|
|
11751
|
+
top: 0,
|
|
11752
|
+
left: 0,
|
|
11753
|
+
width: "100%",
|
|
11754
|
+
height: "100%",
|
|
11755
|
+
backgroundColor: "var(--bw-overlay-color, rgba(15, 23, 42, 0.8))",
|
|
11756
|
+
borderRadius: "var(--bw-border-radius)",
|
|
11757
|
+
display: "flex",
|
|
11758
|
+
alignItems: "center",
|
|
11759
|
+
justifyContent: "center",
|
|
11760
|
+
}, children: jsx("div", { style: {
|
|
11761
|
+
width: "32px",
|
|
11762
|
+
height: "32px",
|
|
11763
|
+
color: "var(--bw-highlight-color-muted, rgba(59, 130, 246, 0.8))",
|
|
11764
|
+
animation: "spin 1s linear infinite",
|
|
11765
|
+
fontSize: "32px",
|
|
11766
|
+
}, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants }), jsxs("div", { style: {
|
|
11767
|
+
display: "flex",
|
|
11768
|
+
justifyContent: "space-between",
|
|
11769
|
+
width: "100%",
|
|
11770
|
+
alignItems: "start",
|
|
11771
|
+
gap: "12px",
|
|
11772
|
+
marginBottom: "4px",
|
|
11773
|
+
}, children: [jsxs("div", { style: { display: "flex", alignItems: "start", gap: "12px" }, children: [jsx("div", { className: "bw-event-instance-datebox", style: {
|
|
11774
|
+
fontSize: "var(--bw-font-size)",
|
|
11775
|
+
transition: "all 0.2s ease",
|
|
11776
|
+
borderRadius: "var(--bw-border-radius-small)",
|
|
11777
|
+
borderTop: `4px solid var(--bw-border-color)`,
|
|
11778
|
+
border: "1px solid var(--bw-border-color)",
|
|
11779
|
+
width: "40px",
|
|
11780
|
+
height: "40px",
|
|
11781
|
+
display: "flex",
|
|
11782
|
+
alignItems: "center",
|
|
11783
|
+
justifyContent: "center",
|
|
11784
|
+
fontWeight: "bold",
|
|
11785
|
+
color: "var(--bw-text-color)",
|
|
11786
|
+
backgroundColor: "var(--bw-background-color)",
|
|
11787
|
+
}, children: startDate.getDate() }), jsxs("div", { style: {
|
|
11788
|
+
fontSize: "var(--bw-font-size)",
|
|
11789
|
+
color: "var(--bw-text-color)",
|
|
11790
|
+
display: "flex",
|
|
11791
|
+
flexDirection: "column",
|
|
11792
|
+
alignItems: "start",
|
|
11793
|
+
justifyContent: "start",
|
|
11794
|
+
lineHeight: "1.2",
|
|
11795
|
+
}, children: [jsxs("div", { children: [jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.startTime) }), formatWeekday(event.startTime) !==
|
|
11796
|
+
formatWeekday(event.endTime) && (jsxs(Fragment, { children: [jsx("span", { style: {
|
|
11797
|
+
color: "var(--bw-text-muted)",
|
|
11798
|
+
fontSize: "14px",
|
|
11799
|
+
}, children: " - " }), jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.endTime) })] }))] }), jsx("div", { children: formatWeekday(event.startTime) ===
|
|
11800
|
+
formatWeekday(event.endTime) ? (jsxs(Fragment, { children: [jsx("span", { style: {
|
|
11801
|
+
color: "var(--bw-text-muted)",
|
|
11802
|
+
fontSize: "14px",
|
|
11803
|
+
}, children: formatTime(event.startTime) }), jsx("span", { style: {
|
|
11804
|
+
color: "var(--bw-text-muted)",
|
|
11805
|
+
fontSize: "14px",
|
|
11806
|
+
}, children: " - " }), jsx("span", { style: {
|
|
11807
|
+
color: "var(--bw-text-muted)",
|
|
11808
|
+
fontSize: "14px",
|
|
11809
|
+
}, children: formatTime(event.endTime) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime), " Uhr"] })) })] }), jsxs("span", { style: {
|
|
11810
|
+
fontSize: "12px",
|
|
11811
|
+
fontWeight: 400,
|
|
11812
|
+
color: "var(--bw-text-muted)",
|
|
11813
|
+
marginLeft: "6px",
|
|
11814
|
+
background: "var(--bw-background-muted)",
|
|
11815
|
+
whiteSpace: "nowrap",
|
|
11816
|
+
}, children: [event.durationDays, " Tag", event.durationDays > 1 ? "e" : ""] })] }), jsx("div", { className: "bw-event-instance-price", style: {
|
|
11817
|
+
textAlign: "right",
|
|
11818
|
+
display: "flex",
|
|
11819
|
+
flexDirection: "column",
|
|
11820
|
+
alignItems: "end",
|
|
11821
|
+
}, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { className: "bw-event-instance-title", style: {
|
|
11822
|
+
fontSize: "var(--bw-font-size)",
|
|
11823
|
+
fontWeight: "600",
|
|
11824
|
+
color: "var(--bw-text-color)",
|
|
11825
|
+
lineHeight: "1.25",
|
|
11826
|
+
margin: "0 0 2px 0",
|
|
11827
|
+
display: "flex",
|
|
11828
|
+
alignItems: "center",
|
|
11829
|
+
gap: "8px",
|
|
11830
|
+
maxWidth: "230px",
|
|
11831
|
+
}, children: event.name }))] }, event.id));
|
|
11832
|
+
}) }) })] }, key));
|
|
11833
|
+
}) }) }) })] }));
|
|
11418
11834
|
}
|
|
11419
11835
|
|
|
11420
11836
|
// Loading skeleton component for NextEventsPreview
|
|
@@ -11809,6 +12225,263 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
|
|
|
11809
12225
|
` })] }));
|
|
11810
12226
|
}
|
|
11811
12227
|
|
|
12228
|
+
function PromoDialog({ onClose, onCtaClick }) {
|
|
12229
|
+
const [copied, setCopied] = useState(false);
|
|
12230
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
12231
|
+
// Hardcoded Xmas surf school content
|
|
12232
|
+
const discountCode = "X-MAS";
|
|
12233
|
+
// Animate in on mount
|
|
12234
|
+
useEffect(() => {
|
|
12235
|
+
const timer = setTimeout(() => setIsVisible(true), 50);
|
|
12236
|
+
return () => clearTimeout(timer);
|
|
12237
|
+
}, []);
|
|
12238
|
+
const handleCopyCode = async () => {
|
|
12239
|
+
try {
|
|
12240
|
+
await navigator.clipboard.writeText(discountCode);
|
|
12241
|
+
setCopied(true);
|
|
12242
|
+
setTimeout(() => setCopied(false), 2000);
|
|
12243
|
+
}
|
|
12244
|
+
catch (err) {
|
|
12245
|
+
// Fallback for older browsers
|
|
12246
|
+
const textArea = document.createElement("textarea");
|
|
12247
|
+
textArea.value = discountCode;
|
|
12248
|
+
document.body.appendChild(textArea);
|
|
12249
|
+
textArea.select();
|
|
12250
|
+
document.execCommand("copy");
|
|
12251
|
+
document.body.removeChild(textArea);
|
|
12252
|
+
setCopied(true);
|
|
12253
|
+
setTimeout(() => setCopied(false), 2000);
|
|
12254
|
+
}
|
|
12255
|
+
};
|
|
12256
|
+
const handleClose = () => {
|
|
12257
|
+
setIsVisible(false);
|
|
12258
|
+
setTimeout(onClose, 200);
|
|
12259
|
+
};
|
|
12260
|
+
const handleCtaClick = () => {
|
|
12261
|
+
setIsVisible(false);
|
|
12262
|
+
setTimeout(onCtaClick, 200);
|
|
12263
|
+
};
|
|
12264
|
+
return (jsxs(Fragment, { children: [jsx("style", { children: `
|
|
12265
|
+
@keyframes promo-wave {
|
|
12266
|
+
0%, 100% { transform: translateX(0) translateY(0); }
|
|
12267
|
+
25% { transform: translateX(5px) translateY(-3px); }
|
|
12268
|
+
50% { transform: translateX(0) translateY(-5px); }
|
|
12269
|
+
75% { transform: translateX(-5px) translateY(-3px); }
|
|
12270
|
+
}
|
|
12271
|
+
@keyframes promo-float {
|
|
12272
|
+
0%, 100% { transform: translateY(0); }
|
|
12273
|
+
50% { transform: translateY(-8px); }
|
|
12274
|
+
}
|
|
12275
|
+
@keyframes promo-shimmer {
|
|
12276
|
+
0% { background-position: -200% center; }
|
|
12277
|
+
100% { background-position: 200% center; }
|
|
12278
|
+
}
|
|
12279
|
+
@keyframes promo-sparkle {
|
|
12280
|
+
0%, 100% { opacity: 0.3; transform: scale(1); }
|
|
12281
|
+
50% { opacity: 1; transform: scale(1.2); }
|
|
12282
|
+
}
|
|
12283
|
+
@keyframes promo-snow {
|
|
12284
|
+
0% { transform: translateY(-10px) rotate(0deg); opacity: 0; }
|
|
12285
|
+
10% { opacity: 1; }
|
|
12286
|
+
90% { opacity: 1; }
|
|
12287
|
+
100% { transform: translateY(350px) rotate(360deg); opacity: 0; }
|
|
12288
|
+
}
|
|
12289
|
+
` }), jsx("div", { onClick: handleClose, style: {
|
|
12290
|
+
position: "fixed",
|
|
12291
|
+
inset: 0,
|
|
12292
|
+
backgroundColor: "rgba(0, 20, 40, 0.85)",
|
|
12293
|
+
backdropFilter: "blur(8px)",
|
|
12294
|
+
zIndex: 9998,
|
|
12295
|
+
opacity: isVisible ? 1 : 0,
|
|
12296
|
+
transition: "opacity 300ms ease-out",
|
|
12297
|
+
} }), jsx("div", { style: {
|
|
12298
|
+
position: "fixed",
|
|
12299
|
+
top: "50%",
|
|
12300
|
+
left: "50%",
|
|
12301
|
+
transform: `translate(-50%, -50%) scale(${isVisible ? 1 : 0.9})`,
|
|
12302
|
+
zIndex: 9999,
|
|
12303
|
+
width: "92%",
|
|
12304
|
+
maxWidth: "440px",
|
|
12305
|
+
opacity: isVisible ? 1 : 0,
|
|
12306
|
+
transition: "all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
12307
|
+
}, children: jsxs("div", { style: {
|
|
12308
|
+
position: "relative",
|
|
12309
|
+
background: "linear-gradient(165deg, #0c4a6e 0%, #0e7490 40%, #0891b2 100%)",
|
|
12310
|
+
borderRadius: "28px",
|
|
12311
|
+
overflow: "hidden",
|
|
12312
|
+
boxShadow: "0 25px 60px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255,255,255,0.1), inset 0 1px 0 rgba(255,255,255,0.2)",
|
|
12313
|
+
}, children: [Array.from({ length: 15 }).map((_, i) => (jsx("div", { style: {
|
|
12314
|
+
position: "absolute",
|
|
12315
|
+
left: `${5 + Math.random() * 90}%`,
|
|
12316
|
+
top: "-10px",
|
|
12317
|
+
fontSize: `${10 + Math.random() * 14}px`,
|
|
12318
|
+
color: "white",
|
|
12319
|
+
opacity: 0,
|
|
12320
|
+
animation: `promo-snow ${4 + Math.random() * 3}s linear infinite`,
|
|
12321
|
+
animationDelay: `${Math.random() * 4}s`,
|
|
12322
|
+
pointerEvents: "none",
|
|
12323
|
+
zIndex: 1,
|
|
12324
|
+
}, children: "\u2744" }, i))), jsxs("div", { style: {
|
|
12325
|
+
position: "relative",
|
|
12326
|
+
height: "180px",
|
|
12327
|
+
background: "linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(12,74,110,0.8) 100%)",
|
|
12328
|
+
display: "flex",
|
|
12329
|
+
alignItems: "center",
|
|
12330
|
+
justifyContent: "center",
|
|
12331
|
+
overflow: "hidden",
|
|
12332
|
+
}, children: [jsx("img", { src: "https://images.unsplash.com/photo-1502680390469-be75c86b636f?w=600&q=80", alt: "Surfer at sunset", style: {
|
|
12333
|
+
position: "absolute",
|
|
12334
|
+
inset: 0,
|
|
12335
|
+
width: "100%",
|
|
12336
|
+
height: "100%",
|
|
12337
|
+
objectFit: "cover",
|
|
12338
|
+
opacity: 0.6,
|
|
12339
|
+
} }), jsx("div", { style: {
|
|
12340
|
+
position: "absolute",
|
|
12341
|
+
inset: 0,
|
|
12342
|
+
background: "linear-gradient(180deg, rgba(12,74,110,0.3) 0%, rgba(12,74,110,0.95) 100%)",
|
|
12343
|
+
} }), jsx("div", { style: {
|
|
12344
|
+
position: "relative",
|
|
12345
|
+
zIndex: 2,
|
|
12346
|
+
fontSize: "64px",
|
|
12347
|
+
animation: "promo-float 3s ease-in-out infinite",
|
|
12348
|
+
filter: "drop-shadow(0 8px 16px rgba(0,0,0,0.4))",
|
|
12349
|
+
}, children: "\uD83C\uDFC4\u200D\u2642\uFE0F" }), jsx("div", { style: {
|
|
12350
|
+
position: "absolute",
|
|
12351
|
+
top: "16px",
|
|
12352
|
+
left: "20px",
|
|
12353
|
+
fontSize: "28px",
|
|
12354
|
+
animation: "promo-sparkle 2s ease-in-out infinite",
|
|
12355
|
+
}, children: "\uD83C\uDF84" }), jsx("div", { style: {
|
|
12356
|
+
position: "absolute",
|
|
12357
|
+
top: "20px",
|
|
12358
|
+
right: "20px",
|
|
12359
|
+
fontSize: "24px",
|
|
12360
|
+
animation: "promo-sparkle 2s ease-in-out infinite 0.5s",
|
|
12361
|
+
}, children: "\u2B50" })] }), jsx("button", { onClick: handleClose, style: {
|
|
12362
|
+
position: "absolute",
|
|
12363
|
+
top: "16px",
|
|
12364
|
+
right: "16px",
|
|
12365
|
+
width: "36px",
|
|
12366
|
+
height: "36px",
|
|
12367
|
+
borderRadius: "50%",
|
|
12368
|
+
border: "none",
|
|
12369
|
+
background: "rgba(0, 0, 0, 0.3)",
|
|
12370
|
+
backdropFilter: "blur(4px)",
|
|
12371
|
+
color: "white",
|
|
12372
|
+
fontSize: "22px",
|
|
12373
|
+
cursor: "pointer",
|
|
12374
|
+
display: "flex",
|
|
12375
|
+
alignItems: "center",
|
|
12376
|
+
justifyContent: "center",
|
|
12377
|
+
transition: "all 150ms ease",
|
|
12378
|
+
zIndex: 10,
|
|
12379
|
+
lineHeight: 1,
|
|
12380
|
+
}, onMouseEnter: (e) => {
|
|
12381
|
+
e.currentTarget.style.background = "rgba(0, 0, 0, 0.5)";
|
|
12382
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
12383
|
+
}, onMouseLeave: (e) => {
|
|
12384
|
+
e.currentTarget.style.background = "rgba(0, 0, 0, 0.3)";
|
|
12385
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
12386
|
+
}, children: "\u00D7" }), jsxs("div", { style: { padding: "28px 28px 32px", textAlign: "center", position: "relative", zIndex: 2 }, children: [jsx("h2", { style: {
|
|
12387
|
+
fontSize: "26px",
|
|
12388
|
+
fontWeight: "800",
|
|
12389
|
+
color: "white",
|
|
12390
|
+
marginBottom: "6px",
|
|
12391
|
+
textShadow: "0 2px 8px rgba(0,0,0,0.3)",
|
|
12392
|
+
letterSpacing: "-0.5px",
|
|
12393
|
+
}, children: "Frohe Weihnachten! \uD83C\uDF85" }), jsxs("p", { style: {
|
|
12394
|
+
fontSize: "17px",
|
|
12395
|
+
color: "rgba(255, 255, 255, 0.9)",
|
|
12396
|
+
marginBottom: "20px",
|
|
12397
|
+
lineHeight: 1.5,
|
|
12398
|
+
}, children: ["Schenk dir oder deinen Liebsten", jsx("br", {}), jsx("strong", { style: { color: "#fbbf24" }, children: "10% Rabatt" }), " auf alle Kurse!"] }), jsxs("div", { style: {
|
|
12399
|
+
background: "white",
|
|
12400
|
+
borderRadius: "16px",
|
|
12401
|
+
padding: "18px 20px",
|
|
12402
|
+
marginBottom: "20px",
|
|
12403
|
+
boxShadow: "0 8px 24px rgba(0,0,0,0.15), inset 0 -2px 0 rgba(0,0,0,0.05)",
|
|
12404
|
+
}, children: [jsx("p", { style: {
|
|
12405
|
+
fontSize: "11px",
|
|
12406
|
+
textTransform: "uppercase",
|
|
12407
|
+
letterSpacing: "1.5px",
|
|
12408
|
+
color: "#64748b",
|
|
12409
|
+
marginBottom: "10px",
|
|
12410
|
+
fontWeight: "600",
|
|
12411
|
+
}, children: "Dein Geschenk-Code" }), jsxs("div", { style: {
|
|
12412
|
+
display: "flex",
|
|
12413
|
+
alignItems: "center",
|
|
12414
|
+
justifyContent: "center",
|
|
12415
|
+
gap: "14px",
|
|
12416
|
+
}, children: [jsx("div", { style: {
|
|
12417
|
+
background: "linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)",
|
|
12418
|
+
padding: "10px 20px",
|
|
12419
|
+
borderRadius: "10px",
|
|
12420
|
+
boxShadow: "0 4px 12px rgba(220, 38, 38, 0.3)",
|
|
12421
|
+
}, children: jsx("span", { style: {
|
|
12422
|
+
fontSize: "28px",
|
|
12423
|
+
fontWeight: "900",
|
|
12424
|
+
color: "white",
|
|
12425
|
+
letterSpacing: "6px",
|
|
12426
|
+
textShadow: "0 2px 4px rgba(0,0,0,0.2)",
|
|
12427
|
+
}, children: discountCode }) }), jsx("button", { onClick: handleCopyCode, style: {
|
|
12428
|
+
padding: "12px 16px",
|
|
12429
|
+
borderRadius: "10px",
|
|
12430
|
+
border: "2px solid",
|
|
12431
|
+
borderColor: copied ? "#22c55e" : "#e2e8f0",
|
|
12432
|
+
background: copied ? "#dcfce7" : "#f8fafc",
|
|
12433
|
+
color: copied ? "#15803d" : "#475569",
|
|
12434
|
+
fontSize: "13px",
|
|
12435
|
+
fontWeight: "600",
|
|
12436
|
+
cursor: "pointer",
|
|
12437
|
+
transition: "all 150ms ease",
|
|
12438
|
+
display: "flex",
|
|
12439
|
+
alignItems: "center",
|
|
12440
|
+
gap: "6px",
|
|
12441
|
+
whiteSpace: "nowrap",
|
|
12442
|
+
}, children: copied ? (jsx(Fragment, { children: "\u2713 Kopiert!" })) : (jsxs(Fragment, { children: [jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: [jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }), "Kopieren"] })) })] })] }), jsx("div", { style: {
|
|
12443
|
+
display: "flex",
|
|
12444
|
+
justifyContent: "center",
|
|
12445
|
+
gap: "8px",
|
|
12446
|
+
marginBottom: "20px",
|
|
12447
|
+
flexWrap: "wrap",
|
|
12448
|
+
}, children: ["🏄 Surfen", "🪁 Wingen", "🏄♀️ SUP", "💨 Windsurfen"].map((activity) => (jsx("span", { style: {
|
|
12449
|
+
background: "rgba(255,255,255,0.15)",
|
|
12450
|
+
backdropFilter: "blur(4px)",
|
|
12451
|
+
padding: "6px 12px",
|
|
12452
|
+
borderRadius: "20px",
|
|
12453
|
+
fontSize: "13px",
|
|
12454
|
+
color: "white",
|
|
12455
|
+
fontWeight: "500",
|
|
12456
|
+
}, children: activity }, activity))) }), jsxs("button", { onClick: handleCtaClick, style: {
|
|
12457
|
+
width: "100%",
|
|
12458
|
+
padding: "18px 24px",
|
|
12459
|
+
borderRadius: "14px",
|
|
12460
|
+
border: "none",
|
|
12461
|
+
background: "linear-gradient(135deg, #f59e0b 0%, #d97706 100%)",
|
|
12462
|
+
color: "white",
|
|
12463
|
+
fontSize: "18px",
|
|
12464
|
+
fontWeight: "700",
|
|
12465
|
+
cursor: "pointer",
|
|
12466
|
+
transition: "all 150ms ease",
|
|
12467
|
+
boxShadow: "0 8px 24px rgba(245, 158, 11, 0.4), inset 0 1px 0 rgba(255,255,255,0.2)",
|
|
12468
|
+
display: "flex",
|
|
12469
|
+
alignItems: "center",
|
|
12470
|
+
justifyContent: "center",
|
|
12471
|
+
gap: "10px",
|
|
12472
|
+
}, onMouseEnter: (e) => {
|
|
12473
|
+
e.currentTarget.style.transform = "translateY(-2px)";
|
|
12474
|
+
e.currentTarget.style.boxShadow = "0 12px 28px rgba(245, 158, 11, 0.5), inset 0 1px 0 rgba(255,255,255,0.2)";
|
|
12475
|
+
}, onMouseLeave: (e) => {
|
|
12476
|
+
e.currentTarget.style.transform = "translateY(0)";
|
|
12477
|
+
e.currentTarget.style.boxShadow = "0 8px 24px rgba(245, 158, 11, 0.4), inset 0 1px 0 rgba(255,255,255,0.2)";
|
|
12478
|
+
}, children: [jsx("span", { style: { animation: "promo-wave 2s ease-in-out infinite" }, children: "\uD83C\uDF81" }), "Jetzt Kurs buchen", jsx("span", { children: "\u2192" })] }), jsx("p", { style: {
|
|
12479
|
+
marginTop: "16px",
|
|
12480
|
+
fontSize: "12px",
|
|
12481
|
+
color: "rgba(255,255,255,0.6)",
|
|
12482
|
+
}, children: "G\u00FCltig f\u00FCr alle Buchungen bis 31. Dezember 2025" })] })] }) })] }));
|
|
12483
|
+
}
|
|
12484
|
+
|
|
11812
12485
|
// Predefined themes & Style Provider have been moved to ../styles/StyleProvider.tsx
|
|
11813
12486
|
// Main widget component
|
|
11814
12487
|
function UniversalBookingWidget({ config: baseConfig }) {
|
|
@@ -11851,6 +12524,9 @@ function UniversalBookingWidget({ config: baseConfig }) {
|
|
|
11851
12524
|
// PERFORMANCE OPTIMIZATION: Lazy component loading
|
|
11852
12525
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
11853
12526
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
|
|
12527
|
+
// Promo dialog state
|
|
12528
|
+
const [showPromoDialog, setShowPromoDialog] = useState(false);
|
|
12529
|
+
const [widgetContainerRef, setWidgetContainerRef] = useState(null);
|
|
11854
12530
|
// Determine initial step and load data
|
|
11855
12531
|
useEffect(() => {
|
|
11856
12532
|
const initializeWidget = async () => {
|
|
@@ -11925,6 +12601,46 @@ function UniversalBookingWidget({ config: baseConfig }) {
|
|
|
11925
12601
|
setShouldRenderBookingForm(true);
|
|
11926
12602
|
}
|
|
11927
12603
|
}, [currentStep, shouldRenderInstanceSelection, shouldRenderBookingForm]);
|
|
12604
|
+
// Promo dialog: show Xmas promo once per user during holiday season, prevent double-opening across multiple widgets
|
|
12605
|
+
useEffect(() => {
|
|
12606
|
+
// Only show during holiday season (December and January)
|
|
12607
|
+
const now = new Date();
|
|
12608
|
+
const month = now.getMonth(); // 0 = January, 11 = December
|
|
12609
|
+
const isHolidaySeason = month === 11 || month === 0; // December (11) or January (0)
|
|
12610
|
+
if (!isHolidaySeason) {
|
|
12611
|
+
return;
|
|
12612
|
+
}
|
|
12613
|
+
const promoId = "xmas-2024";
|
|
12614
|
+
const storageKey = `bigz-promo-${promoId}-shown`;
|
|
12615
|
+
const globalFlagKey = `__bigzPromoShown_${promoId}`;
|
|
12616
|
+
// Check if already shown in this session (localStorage) or claimed by another widget (global flag)
|
|
12617
|
+
const alreadyShown = localStorage.getItem(storageKey) === "true";
|
|
12618
|
+
const claimedByOtherWidget = window[globalFlagKey] === true;
|
|
12619
|
+
if (alreadyShown || claimedByOtherWidget) {
|
|
12620
|
+
return;
|
|
12621
|
+
}
|
|
12622
|
+
// Claim this promo for this widget instance (prevents other widgets from showing it)
|
|
12623
|
+
window[globalFlagKey] = true;
|
|
12624
|
+
// Show the dialog after a short delay for better UX
|
|
12625
|
+
const timer = setTimeout(() => {
|
|
12626
|
+
setShowPromoDialog(true);
|
|
12627
|
+
}, 1000);
|
|
12628
|
+
return () => clearTimeout(timer);
|
|
12629
|
+
}, []);
|
|
12630
|
+
// Handle promo dialog close
|
|
12631
|
+
const handlePromoDialogClose = () => {
|
|
12632
|
+
setShowPromoDialog(false);
|
|
12633
|
+
localStorage.setItem("bigz-promo-xmas-2024-shown", "true");
|
|
12634
|
+
};
|
|
12635
|
+
// Handle promo dialog CTA click - scroll to widget
|
|
12636
|
+
const handlePromoCtaClick = () => {
|
|
12637
|
+
setShowPromoDialog(false);
|
|
12638
|
+
localStorage.setItem("bigz-promo-xmas-2024-shown", "true");
|
|
12639
|
+
// Scroll to the widget container
|
|
12640
|
+
if (widgetContainerRef) {
|
|
12641
|
+
widgetContainerRef.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
12642
|
+
}
|
|
12643
|
+
};
|
|
11928
12644
|
const loadEventTypes = async () => {
|
|
11929
12645
|
const requestBody = {
|
|
11930
12646
|
organizationId: config.organizationId,
|
|
@@ -12324,104 +13040,104 @@ function UniversalBookingWidget({ config: baseConfig }) {
|
|
|
12324
13040
|
// Main view based on view mode
|
|
12325
13041
|
if (viewMode === "next-events" && showingPreview) {
|
|
12326
13042
|
// Next events preview mode
|
|
12327
|
-
return (jsxs(StyleProvider, { config: config, children: [jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => {
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
13043
|
+
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => {
|
|
13044
|
+
setCurrentStep("eventTypes");
|
|
13045
|
+
setShowingPreview(true);
|
|
13046
|
+
setEventDetails(null);
|
|
13047
|
+
}, onBackToEventTypes: () => {
|
|
13048
|
+
setCurrentStep("eventTypes");
|
|
13049
|
+
setShowingPreview(true);
|
|
13050
|
+
setEventDetails(null);
|
|
13051
|
+
}, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => {
|
|
13052
|
+
setCurrentStep("eventTypes");
|
|
13053
|
+
setShowingPreview(true);
|
|
13054
|
+
setEventDetails(null);
|
|
13055
|
+
}, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
13056
|
+
setIsSuccess(false);
|
|
13057
|
+
setCurrentStep("eventTypes");
|
|
13058
|
+
setShowingPreview(true);
|
|
13059
|
+
// Reset state
|
|
13060
|
+
setSuccessPaymentIntentId(null);
|
|
13061
|
+
// Reset lazy loading flags
|
|
13062
|
+
setShouldRenderInstanceSelection(false);
|
|
13063
|
+
setShouldRenderBookingForm(false);
|
|
13064
|
+
// Clean up URL to remove Stripe parameters
|
|
13065
|
+
const url = new URL(window.location.href);
|
|
13066
|
+
url.searchParams.delete("payment_intent");
|
|
13067
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
13068
|
+
url.searchParams.delete("redirect_status");
|
|
13069
|
+
window.history.replaceState({}, "", url.toString());
|
|
13070
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && (jsx(PromoDialog, { onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
12355
13071
|
}
|
|
12356
13072
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
12357
13073
|
// Show all events for the single event type
|
|
12358
|
-
return (jsxs(StyleProvider, { config: config, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
13074
|
+
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
13075
|
+
setShowingPreview(true);
|
|
13076
|
+
setCurrentStep("eventTypes");
|
|
13077
|
+
}, isOpen: currentStep === "eventInstances", onClose: () => {
|
|
13078
|
+
setShowingPreview(true);
|
|
13079
|
+
setCurrentStep("eventTypes");
|
|
13080
|
+
}, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
13081
|
+
setIsSuccess(false);
|
|
13082
|
+
setCurrentStep("eventTypes");
|
|
13083
|
+
setShowingPreview(true);
|
|
13084
|
+
// Reset state
|
|
13085
|
+
setSuccessPaymentIntentId(null);
|
|
13086
|
+
// Reset lazy loading flags
|
|
13087
|
+
setShouldRenderInstanceSelection(false);
|
|
13088
|
+
setShouldRenderBookingForm(false);
|
|
13089
|
+
// Clean up URL to remove Stripe parameters
|
|
13090
|
+
const url = new URL(window.location.href);
|
|
13091
|
+
url.searchParams.delete("payment_intent");
|
|
13092
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
13093
|
+
url.searchParams.delete("redirect_status");
|
|
13094
|
+
window.history.replaceState({}, "", url.toString());
|
|
13095
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && (jsx(PromoDialog, { onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
12380
13096
|
}
|
|
12381
13097
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
12382
13098
|
// Button mode - show button that opens sidebar/booking directly
|
|
12383
|
-
return (
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
13099
|
+
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
13100
|
+
display: "flex",
|
|
13101
|
+
justifyContent: "center",
|
|
13102
|
+
alignItems: "center",
|
|
13103
|
+
minHeight: "120px",
|
|
13104
|
+
}, children: [jsx("button", { type: "button", style: {
|
|
13105
|
+
backgroundColor: "var(--bw-highlight-color)",
|
|
13106
|
+
color: "white",
|
|
13107
|
+
padding: "16px 32px",
|
|
13108
|
+
border: "none",
|
|
13109
|
+
borderRadius: "var(--bw-border-radius)",
|
|
13110
|
+
fontSize: "18px",
|
|
13111
|
+
fontWeight: 600,
|
|
13112
|
+
fontFamily: "var(--bw-font-family)",
|
|
13113
|
+
boxShadow: "var(--bw-shadow-md)",
|
|
13114
|
+
cursor: "pointer",
|
|
13115
|
+
}, onClick: () => {
|
|
13116
|
+
if (isDirectInstanceMode) {
|
|
13117
|
+
setCurrentStep("booking");
|
|
13118
|
+
setShouldRenderBookingForm(true);
|
|
13119
|
+
}
|
|
13120
|
+
else {
|
|
13121
|
+
setSidebarOpen(true);
|
|
13122
|
+
setShouldRenderInstanceSelection(true);
|
|
13123
|
+
}
|
|
13124
|
+
}, children: config.buttonText ||
|
|
13125
|
+
(isDirectInstanceMode ? "Jetzt buchen" : "Jetzt Termin auswählen") }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen, onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), 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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
13126
|
+
setIsSuccess(false);
|
|
13127
|
+
setCurrentStep("eventTypes");
|
|
13128
|
+
setSidebarOpen(false);
|
|
13129
|
+
// Reset state
|
|
13130
|
+
setSuccessPaymentIntentId(null);
|
|
13131
|
+
// Reset lazy loading flags
|
|
13132
|
+
setShouldRenderInstanceSelection(false);
|
|
13133
|
+
setShouldRenderBookingForm(false);
|
|
13134
|
+
// Clean up URL to remove Stripe parameters
|
|
13135
|
+
const url = new URL(window.location.href);
|
|
13136
|
+
url.searchParams.delete("payment_intent");
|
|
13137
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
13138
|
+
url.searchParams.delete("redirect_status");
|
|
13139
|
+
window.history.replaceState({}, "", url.toString());
|
|
13140
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && (jsx(PromoDialog, { onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
12425
13141
|
}
|
|
12426
13142
|
// Cards mode (default) - show event type selection
|
|
12427
13143
|
const cardsView = (jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount() }));
|
|
@@ -12454,21 +13170,21 @@ function UniversalBookingWidget({ config: baseConfig }) {
|
|
|
12454
13170
|
};
|
|
12455
13171
|
};
|
|
12456
13172
|
const backHandlers = getBackHandlers();
|
|
12457
|
-
return (jsxs(StyleProvider, { config: config, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), 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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
13173
|
+
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 })), 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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
|
|
13174
|
+
setIsSuccess(false);
|
|
13175
|
+
setCurrentStep("eventTypes");
|
|
13176
|
+
// Reset state
|
|
13177
|
+
setSuccessPaymentIntentId(null);
|
|
13178
|
+
// Reset lazy loading flags
|
|
13179
|
+
setShouldRenderInstanceSelection(false);
|
|
13180
|
+
setShouldRenderBookingForm(false);
|
|
13181
|
+
// Clean up URL to remove Stripe parameters
|
|
13182
|
+
const url = new URL(window.location.href);
|
|
13183
|
+
url.searchParams.delete("payment_intent");
|
|
13184
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
13185
|
+
url.searchParams.delete("redirect_status");
|
|
13186
|
+
window.history.replaceState({}, "", url.toString());
|
|
13187
|
+
}, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }), showPromoDialog && (jsx(PromoDialog, { onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
12472
13188
|
}
|
|
12473
13189
|
|
|
12474
13190
|
// Export init function for vanilla JS usage
|