@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/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$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" }) }));
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$1, { size: 16, color: "var(--bw-success-color)" }) }), jsx("span", { children: highlight.trim() })] }, index))) }) }) }))] }), eventType.description && (jsxs("div", { style: {
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
- const watchedDiscountCode = form.watch("discountCode");
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 amount and deposit amount
9697
- const calculateTotal = () => {
10034
+ // Calculate base total before any discounts
10035
+ const calculateBaseTotal = useCallback(() => {
9698
10036
  if (!eventDetails)
9699
10037
  return 0;
9700
- const baseTotal = eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
9701
- return discountCode ? discountCode.newTotal : baseTotal;
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
- const paymentAmount = depositAmount > 0 ? depositAmount : totalAmount;
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
- // Validate discount codes
10101
+ // Re-validate vouchers when participant count changes (affects order value)
9726
10102
  useEffect(() => {
9727
- const validateDiscountCode = async (code) => {
9728
- if (!code.trim() || !eventDetails) {
9729
- setDiscountCode(null);
9730
- setDiscountError(null);
9731
- return;
9732
- }
9733
- setDiscountLoading(true);
9734
- setDiscountError(null);
9735
- try {
9736
- const baseTotal = eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
9737
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/validate-discount"), {
9738
- method: "POST",
9739
- headers: createApiHeaders(config),
9740
- body: JSON.stringify(createRequestBody(config, {
9741
- code: code.trim(),
9742
- orderValue: baseTotal,
9743
- })),
9744
- });
9745
- const data = await response.json();
9746
- if (data.valid) {
9747
- setDiscountCode(data.discountCode);
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
- setDiscountCode(null);
9751
- setDiscountError(data.error);
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
- catch (err) {
9755
- setDiscountError("Fehler beim Validieren des Rabattcodes");
9756
- setDiscountCode(null);
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("form", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxs("div", { style: {
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) })] })), discountCode && (jsxs(Fragment, { children: [jsxs("div", { style: {
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(eventDetails.price *
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: [jsx("span", { style: {
10559
+ }, children: [jsxs("span", { style: {
10182
10560
  color: "var(--bw-success-color)",
10183
10561
  fontFamily: "var(--bw-font-family)",
10184
- }, children: "Rabatt:" }), jsxs("span", { style: {
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(discountCode.discountAmount)] })] })] })), jsxs("div", { style: {
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: discountCode, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, stripePromise: stripePromise, stripeAppearance: stripeAppearance })] }));
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
- ` }), jsxs(Sidebar, { isOpen: isOpen, onClose: handleClose, title: "Termin-Auswahl", children: [jsx("p", { className: "bw-event-instance-title", children: selectedEventType?.name }), jsx("div", { className: "bw-event-instance-list", style: { padding: "24px" }, children: jsx("div", { style: {
11288
- display: "flex",
11289
- flexDirection: "column",
11290
- gap: "20px",
11291
- }, children: monthYearGroups.map(({ key, label, events, minPrice }) => {
11292
- const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
11293
- return (jsx(Accordion, { title: label, priceInfo: jsx("div", { style: {
11294
- fontSize: "1rem",
11295
- backgroundColor: monthPriceDisplayInfo
11296
- ? monthPriceDisplayInfo.backgroundColor
11297
- : "#14532d",
11298
- color: monthPriceDisplayInfo ? monthPriceDisplayInfo.textColor : "#4ade80",
11299
- fontWeight: 500,
11300
- marginLeft: "auto",
11301
- padding: "4px 8px",
11302
- borderRadius: "var(--bw-border-radius-small)",
11303
- border: monthPriceDisplayInfo ? "none" : undefined,
11304
- boxShadow: monthPriceDisplayInfo
11305
- ? "0 2px 4px rgba(0, 0, 0, 0.2)"
11306
- : undefined,
11307
- }, children: `ab ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: {
11308
- display: "flex",
11309
- flexDirection: "column",
11310
- gap: "12px",
11311
- paddingTop: "12px",
11312
- }, children: events.map((event) => {
11313
- const availableSpots = event.maxParticipants - event.participantCount;
11314
- const isFullyBooked = availableSpots === 0;
11315
- const startDate = new Date(event.startTime);
11316
- const isPastEvent = today.toISOString() >= startDate.toISOString();
11317
- return (jsxs("div", { className: "bw-event-instance-card", style: {
11318
- position: "relative",
11319
- cursor: !isFullyBooked && !isPastEvent && event.bookingOpen
11320
- ? "pointer"
11321
- : "not-allowed",
11322
- border: "1px solid var(--bw-border-color)",
11323
- backgroundColor: "var(--bw-surface-color)",
11324
- borderRadius: "var(--bw-border-radius)",
11325
- padding: "16px 20px",
11326
- transition: "all 0.2s ease",
11327
- opacity: isFullyBooked || isPastEvent ? 0.3 : 1,
11328
- filter: isFullyBooked || isPastEvent ? "grayscale(40%)" : "none",
11329
- fontFamily: "var(--bw-font-family)",
11330
- }, onClick: () => {
11331
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11332
- handleEventInstanceSelect(event);
11333
- }
11334
- }, onMouseEnter: (e) => {
11335
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11336
- e.currentTarget.style.transform = "scale(1.02)";
11337
- e.currentTarget.style.backgroundColor =
11338
- "var(--bw-surface-muted, rgba(59, 130, 246, 0.1))";
11339
- }
11340
- }, onMouseLeave: (e) => {
11341
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11342
- e.currentTarget.style.transform = "scale(1)";
11343
- e.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
11344
- }
11345
- }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
11346
- position: "absolute",
11347
- top: 0,
11348
- left: 0,
11349
- width: "100%",
11350
- height: "100%",
11351
- backgroundColor: "var(--bw-overlay-color, rgba(15, 23, 42, 0.8))",
11352
- borderRadius: "var(--bw-border-radius)",
11353
- display: "flex",
11354
- alignItems: "center",
11355
- justifyContent: "center",
11356
- }, children: jsx("div", { style: {
11357
- width: "32px",
11358
- height: "32px",
11359
- color: "var(--bw-highlight-color-muted, rgba(59, 130, 246, 0.8))",
11360
- animation: "spin 1s linear infinite",
11361
- fontSize: "32px",
11362
- }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants }), jsxs("div", { style: {
11363
- display: "flex",
11364
- justifyContent: "space-between",
11365
- width: "100%",
11366
- alignItems: "start",
11367
- gap: "12px",
11368
- marginBottom: "4px",
11369
- }, children: [jsxs("div", { style: { display: "flex", alignItems: "start", gap: "12px" }, children: [jsx("div", { className: "bw-event-instance-datebox", style: {
11370
- fontSize: "var(--bw-font-size)",
11371
- transition: "all 0.2s ease",
11372
- borderRadius: "var(--bw-border-radius-small)",
11373
- borderTop: `4px solid var(--bw-border-color)`,
11374
- border: "1px solid var(--bw-border-color)",
11375
- width: "40px",
11376
- height: "40px",
11377
- display: "flex",
11378
- alignItems: "center",
11379
- justifyContent: "center",
11380
- fontWeight: "bold",
11381
- color: "var(--bw-text-color)",
11382
- backgroundColor: "var(--bw-background-color)",
11383
- }, children: startDate.getDate() }), jsxs("div", { style: {
11384
- fontSize: "var(--bw-font-size)",
11385
- color: "var(--bw-text-color)",
11386
- display: "flex",
11387
- flexDirection: "column",
11388
- alignItems: "start",
11389
- justifyContent: "start",
11390
- lineHeight: "1.2",
11391
- }, children: [jsxs("div", { children: [jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.startTime) }), formatWeekday(event.startTime) !==
11392
- formatWeekday(event.endTime) && (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.endTime) })] }))] }), jsx("div", { children: formatWeekday(event.startTime) ===
11393
- formatWeekday(event.endTime) ? (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime) }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime), " Uhr"] })) })] }), jsxs("span", { style: {
11394
- fontSize: "12px",
11395
- fontWeight: 400,
11396
- color: "var(--bw-text-muted)",
11397
- marginLeft: "6px",
11398
- background: "var(--bw-background-muted)",
11399
- whiteSpace: "nowrap",
11400
- }, children: [event.durationDays, " Tag", event.durationDays > 1 ? "e" : ""] })] }), jsx("div", { className: "bw-event-instance-price", style: {
11401
- textAlign: "right",
11402
- display: "flex",
11403
- flexDirection: "column",
11404
- alignItems: "end",
11405
- }, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { className: "bw-event-instance-title", style: {
11406
- fontSize: "var(--bw-font-size)",
11407
- fontWeight: "600",
11408
- color: "var(--bw-text-color)",
11409
- lineHeight: "1.25",
11410
- margin: "0 0 2px 0",
11411
- display: "flex",
11412
- alignItems: "center",
11413
- gap: "8px",
11414
- maxWidth: "230px",
11415
- }, children: event.name }))] }, event.id));
11416
- }) }) }, key));
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
- setCurrentStep("eventTypes");
12329
- setShowingPreview(true);
12330
- setEventDetails(null);
12331
- }, onBackToEventTypes: () => {
12332
- setCurrentStep("eventTypes");
12333
- setShowingPreview(true);
12334
- setEventDetails(null);
12335
- }, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => {
12336
- setCurrentStep("eventTypes");
12337
- setShowingPreview(true);
12338
- setEventDetails(null);
12339
- }, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
12340
- setIsSuccess(false);
12341
- setCurrentStep("eventTypes");
12342
- setShowingPreview(true);
12343
- // Reset state
12344
- setSuccessPaymentIntentId(null);
12345
- // Reset lazy loading flags
12346
- setShouldRenderInstanceSelection(false);
12347
- setShouldRenderBookingForm(false);
12348
- // Clean up URL to remove Stripe parameters
12349
- const url = new URL(window.location.href);
12350
- url.searchParams.delete("payment_intent");
12351
- url.searchParams.delete("payment_intent_client_secret");
12352
- url.searchParams.delete("redirect_status");
12353
- window.history.replaceState({}, "", url.toString());
12354
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }));
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
- setShowingPreview(true);
12360
- setCurrentStep("eventTypes");
12361
- }, isOpen: currentStep === "eventInstances", onClose: () => {
12362
- setShowingPreview(true);
12363
- setCurrentStep("eventTypes");
12364
- }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
12365
- setIsSuccess(false);
12366
- setCurrentStep("eventTypes");
12367
- setShowingPreview(true);
12368
- // Reset state
12369
- setSuccessPaymentIntentId(null);
12370
- // Reset lazy loading flags
12371
- setShouldRenderInstanceSelection(false);
12372
- setShouldRenderBookingForm(false);
12373
- // Clean up URL to remove Stripe parameters
12374
- const url = new URL(window.location.href);
12375
- url.searchParams.delete("payment_intent");
12376
- url.searchParams.delete("payment_intent_client_secret");
12377
- url.searchParams.delete("redirect_status");
12378
- window.history.replaceState({}, "", url.toString());
12379
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }));
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 (jsx(StyleProvider, { config: config, children: jsxs("div", { style: {
12384
- display: "flex",
12385
- justifyContent: "center",
12386
- alignItems: "center",
12387
- minHeight: "120px",
12388
- }, children: [jsx("button", { type: "button", style: {
12389
- backgroundColor: "var(--bw-highlight-color)",
12390
- color: "white",
12391
- padding: "16px 32px",
12392
- border: "none",
12393
- borderRadius: "var(--bw-border-radius)",
12394
- fontSize: "18px",
12395
- fontWeight: 600,
12396
- fontFamily: "var(--bw-font-family)",
12397
- boxShadow: "var(--bw-shadow-md)",
12398
- cursor: "pointer",
12399
- }, onClick: () => {
12400
- if (isDirectInstanceMode) {
12401
- setCurrentStep("booking");
12402
- setShouldRenderBookingForm(true);
12403
- }
12404
- else {
12405
- setSidebarOpen(true);
12406
- setShouldRenderInstanceSelection(true);
12407
- }
12408
- }, children: config.buttonText ||
12409
- (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: () => {
12410
- setIsSuccess(false);
12411
- setCurrentStep("eventTypes");
12412
- setSidebarOpen(false);
12413
- // Reset state
12414
- setSuccessPaymentIntentId(null);
12415
- // Reset lazy loading flags
12416
- setShouldRenderInstanceSelection(false);
12417
- setShouldRenderBookingForm(false);
12418
- // Clean up URL to remove Stripe parameters
12419
- const url = new URL(window.location.href);
12420
- url.searchParams.delete("payment_intent");
12421
- url.searchParams.delete("payment_intent_client_secret");
12422
- url.searchParams.delete("redirect_status");
12423
- window.history.replaceState({}, "", url.toString());
12424
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }) }));
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
- setIsSuccess(false);
12459
- setCurrentStep("eventTypes");
12460
- // Reset state
12461
- setSuccessPaymentIntentId(null);
12462
- // Reset lazy loading flags
12463
- setShouldRenderInstanceSelection(false);
12464
- setShouldRenderBookingForm(false);
12465
- // Clean up URL to remove Stripe parameters
12466
- const url = new URL(window.location.href);
12467
- url.searchParams.delete("payment_intent");
12468
- url.searchParams.delete("payment_intent_client_secret");
12469
- url.searchParams.delete("redirect_status");
12470
- window.history.replaceState({}, "", url.toString());
12471
- }, config: config, onError: setError, paymentIntentId: successPaymentIntentId })] }));
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