@bigz-app/booking-widget 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6908,7 +6908,7 @@ const preprocessMarkdown$1 = (markdown) => {
6908
6908
  // Convert double underscores to HTML underline tags for React Markdown
6909
6909
  return markdown.replace(/__([^_]+)__/g, "<u>$1</u>");
6910
6910
  };
6911
- const IconCheck$1 = ({ size = 16, color = "#10b981" }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }));
6911
+ const IconCheck$2 = ({ size = 16, color = "#10b981" }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }));
6912
6912
  const IconWave$1 = ({ size = 20, color = "#0ea5e9" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M2 18c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsxRuntime.jsx("path", { d: "M2 12c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsxRuntime.jsx("path", { d: "M2 6c2-2 6-2 8 0s6 2 8 0 6-2 8 0" })] }));
6913
6913
  function EventTypeDetailsDialog({ isOpen, onClose, eventType, onEventTypeSelect, }) {
6914
6914
  if (!isOpen || !eventType)
@@ -6955,7 +6955,7 @@ function EventTypeDetailsDialog({ isOpen, onClose, eventType, onEventTypeSelect,
6955
6955
  fontSize: "16px",
6956
6956
  lineHeight: "1.6",
6957
6957
  color: "var(--bw-text-color)",
6958
- }, children: [jsxRuntime.jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsxRuntime.jsx(IconCheck$1, { size: 16, color: "var(--bw-success-color)" }) }), jsxRuntime.jsx("span", { children: highlight.trim() })] }, index))) }) }) }))] }), eventType.description && (jsxRuntime.jsxs("div", { style: {
6958
+ }, children: [jsxRuntime.jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsxRuntime.jsx(IconCheck$2, { size: 16, color: "var(--bw-success-color)" }) }), jsxRuntime.jsx("span", { children: highlight.trim() })] }, index))) }) }) }))] }), eventType.description && (jsxRuntime.jsxs("div", { style: {
6959
6959
  marginBottom: "24px",
6960
6960
  color: "var(--bw-text-muted)",
6961
6961
  fontSize: "16px",
@@ -7264,7 +7264,7 @@ const preprocessMarkdown = (markdown) => {
7264
7264
  // Custom minimal SVG icons (Lucide-style)
7265
7265
  const IconClock = ({ size = 16, color = "#10b981" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }), jsxRuntime.jsx("polyline", { points: "12 6 12 12 16 14" })] }));
7266
7266
  const IconCalendar = ({ size = 16, color = "#3b82f6" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }), jsxRuntime.jsx("line", { x1: "16", y1: "2", x2: "16", y2: "6" }), jsxRuntime.jsx("line", { x1: "8", y1: "2", x2: "8", y2: "6" }), jsxRuntime.jsx("line", { x1: "3", y1: "10", x2: "21", y2: "10" })] }));
7267
- const IconCheck = ({ size = 16, color = "#10b981" }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }));
7267
+ const IconCheck$1 = ({ size = 16, color = "#10b981" }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }));
7268
7268
  // Wave icon for booking action
7269
7269
  const IconWave = ({ size = 20, color = "#0ea5e9" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M2 18c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsxRuntime.jsx("path", { d: "M2 12c2-2 6-2 8 0s6 2 8 0 6-2 8 0" }), jsxRuntime.jsx("path", { d: "M2 6c2-2 6-2 8 0s6 2 8 0 6-2 8 0" })] }));
7270
7270
  // Loading skeleton component that matches the actual design
@@ -7728,7 +7728,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
7728
7728
  color: "var(--bw-text-muted)",
7729
7729
  position: "relative",
7730
7730
  maxWidth: "100%",
7731
- }, children: [jsxRuntime.jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsxRuntime.jsx(IconCheck, { size: 16, color: "var(--bw-success-color)" }) }), jsxRuntime.jsx("span", { style: {
7731
+ }, children: [jsxRuntime.jsx("div", { style: { marginTop: "4px", flexShrink: 0 }, children: jsxRuntime.jsx(IconCheck$1, { size: 16, color: "var(--bw-success-color)" }) }), jsxRuntime.jsx("span", { style: {
7732
7732
  textOverflow: "ellipsis",
7733
7733
  overflow: "hidden",
7734
7734
  whiteSpace: "nowrap",
@@ -9131,6 +9131,110 @@ var reactStripe_umd = {exports: {}};
9131
9131
 
9132
9132
  var reactStripe_umdExports = reactStripe_umd.exports;
9133
9133
 
9134
+ // Component for bookings fully covered by gift cards (no Stripe payment needed)
9135
+ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, giftCards, onSuccess, onError, }) {
9136
+ const [isLoading, setIsLoading] = React__default.useState(false);
9137
+ const [error, setError] = React__default.useState(null);
9138
+ const handleBooking = async () => {
9139
+ setIsLoading(true);
9140
+ setError(null);
9141
+ try {
9142
+ // Create booking directly without Stripe payment
9143
+ const requestData = {
9144
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
9145
+ organizationId: config.organizationId,
9146
+ participants: formData.participants.filter((p) => p.name?.trim()),
9147
+ discountCode: discountCode?.code,
9148
+ giftCardCodes: giftCards.map((gc) => gc.code),
9149
+ customerName: formData.customerName?.trim(),
9150
+ customerEmail: formData.customerEmail?.trim(),
9151
+ customerPhone: formData.customerPhone?.trim(),
9152
+ comment: formData.comment?.trim(),
9153
+ paymentMethod: "gift_card",
9154
+ };
9155
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
9156
+ method: "POST",
9157
+ headers: createApiHeaders(config),
9158
+ body: JSON.stringify(createRequestBody(config, requestData)),
9159
+ });
9160
+ const data = await response.json();
9161
+ if (response.ok) {
9162
+ onSuccess({
9163
+ booking: data.booking,
9164
+ order: data.order,
9165
+ giftCardRedemptions: data.giftCardRedemptions,
9166
+ });
9167
+ }
9168
+ else {
9169
+ setError(data.error || "Fehler beim Erstellen der Buchung");
9170
+ onError(data.error || "Fehler beim Erstellen der Buchung");
9171
+ }
9172
+ }
9173
+ catch (err) {
9174
+ setError(err.message || "Fehler beim Erstellen der Buchung");
9175
+ onError(err.message || "Fehler beim Erstellen der Buchung");
9176
+ }
9177
+ finally {
9178
+ setIsLoading(false);
9179
+ }
9180
+ };
9181
+ const totalGiftCardAmount = giftCards.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0);
9182
+ return (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing)" }, children: [jsxRuntime.jsxs("div", { style: {
9183
+ backgroundColor: "var(--bw-success-color)15",
9184
+ border: "1px solid var(--bw-success-color)40",
9185
+ borderRadius: "var(--bw-border-radius)",
9186
+ padding: "var(--bw-spacing)",
9187
+ }, children: [jsxRuntime.jsx("div", { style: {
9188
+ display: "flex",
9189
+ alignItems: "center",
9190
+ gap: "8px",
9191
+ marginBottom: "8px",
9192
+ color: "var(--bw-success-color)",
9193
+ fontFamily: "var(--bw-font-family)",
9194
+ fontWeight: "600",
9195
+ }, children: "\uD83C\uDF81 Vollst\u00E4ndig durch Gutschein(e) gedeckt" }), jsxRuntime.jsxs("div", { style: {
9196
+ fontSize: "var(--bw-font-size)",
9197
+ color: "var(--bw-text-muted)",
9198
+ fontFamily: "var(--bw-font-family)",
9199
+ }, children: ["Gutschein-Guthaben: ", formatCurrency(totalGiftCardAmount)] })] }), error && (jsxRuntime.jsxs("div", { style: {
9200
+ backgroundColor: "var(--bw-error-color)15",
9201
+ border: "1px solid var(--bw-error-color)40",
9202
+ borderRadius: "var(--bw-border-radius)",
9203
+ padding: "var(--bw-spacing)",
9204
+ color: "var(--bw-error-color)",
9205
+ fontSize: "var(--bw-font-size)",
9206
+ fontFamily: "var(--bw-font-family)",
9207
+ }, children: ["\u26A0\uFE0F ", error] })), jsxRuntime.jsx("button", { type: "button", onClick: handleBooking, disabled: isLoading, style: {
9208
+ width: "100%",
9209
+ padding: "12px 24px",
9210
+ backgroundColor: "var(--bw-highlight-color)",
9211
+ color: "#fff",
9212
+ border: "none",
9213
+ borderRadius: "var(--bw-border-radius)",
9214
+ fontSize: "var(--bw-font-size)",
9215
+ fontWeight: "600",
9216
+ cursor: isLoading ? "not-allowed" : "pointer",
9217
+ opacity: isLoading ? 0.6 : 1,
9218
+ transition: "all 0.2s ease",
9219
+ fontFamily: "var(--bw-font-family)",
9220
+ display: "flex",
9221
+ alignItems: "center",
9222
+ justifyContent: "center",
9223
+ gap: "8px",
9224
+ }, children: isLoading ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: {
9225
+ width: "16px",
9226
+ height: "16px",
9227
+ border: "2px solid #fff",
9228
+ borderTopColor: "transparent",
9229
+ borderRadius: "50%",
9230
+ animation: "spin 1s linear infinite",
9231
+ } }), "Buchung wird erstellt..."] })) : ("Mit Gutschein buchen") }), jsxRuntime.jsx("style", { children: `
9232
+ @keyframes spin {
9233
+ from { transform: rotate(0deg); }
9234
+ to { transform: rotate(360deg); }
9235
+ }
9236
+ ` })] }));
9237
+ }
9134
9238
  const spinner$1 = (borderColor) => (jsxRuntime.jsx("div", { style: {
9135
9239
  width: "auto",
9136
9240
  height: "auto",
@@ -9147,7 +9251,7 @@ const spinner$1 = (borderColor) => (jsxRuntime.jsx("div", { style: {
9147
9251
  borderRadius: "50%",
9148
9252
  } }) }));
9149
9253
  // Inner component that uses the Stripe hooks
9150
- function PaymentFormInner({ config, eventDetails, formData, totalAmount, discountCode, onSuccess, onError, }) {
9254
+ function PaymentFormInner({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, }) {
9151
9255
  const stripe = reactStripe_umdExports.useStripe();
9152
9256
  const elements = reactStripe_umdExports.useElements();
9153
9257
  const [isLoading, setIsLoading] = React__default.useState(false);
@@ -9278,7 +9382,7 @@ function PaymentFormInner({ config, eventDetails, formData, totalAmount, discoun
9278
9382
  ` })] }));
9279
9383
  }
9280
9384
  // Main PaymentForm component that handles payment intent creation and Elements wrapper
9281
- function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, }) {
9385
+ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, }) {
9282
9386
  const [clientSecret, setClientSecret] = React__default.useState(null);
9283
9387
  const [paymentIntentId, setPaymentIntentId] = React__default.useState(null);
9284
9388
  const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = React__default.useState(false);
@@ -9374,6 +9478,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
9374
9478
  currency: "eur",
9375
9479
  participants: formData.participants.filter((p) => p.name?.trim()),
9376
9480
  discountCode: discountCode?.code,
9481
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
9377
9482
  customerName: formData.customerName?.trim(),
9378
9483
  customerEmail: formData.customerEmail?.trim(),
9379
9484
  customerPhone: formData.customerPhone?.trim(),
@@ -9435,8 +9540,21 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
9435
9540
  formData.customerName,
9436
9541
  totalAmount,
9437
9542
  discountCode,
9543
+ giftCards,
9438
9544
  config,
9439
9545
  ]);
9546
+ // Calculate total gift card coverage
9547
+ const totalGiftCardAmount = giftCards?.reduce((sum, gc) => sum + (gc.balanceToUse || gc.discountAmount || 0), 0) || 0;
9548
+ const baseTotal = eventDetails?.price
9549
+ ? eventDetails.price * (formData.participants?.filter((p) => p.name?.trim()).length || 0)
9550
+ : 0;
9551
+ const discountAmount = discountCode?.discountAmount || 0;
9552
+ const amountAfterDiscount = Math.max(0, baseTotal - discountAmount);
9553
+ const isFullyCoveredByGiftCards = totalGiftCardAmount >= amountAfterDiscount && amountAfterDiscount > 0;
9554
+ // If gift cards fully cover the payment, show a simplified booking button
9555
+ if (isFullyCoveredByGiftCards && totalAmount <= 0) {
9556
+ return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards || [], onSuccess: onSuccess, onError: onError }));
9557
+ }
9440
9558
  // Show loading state while creating payment intent
9441
9559
  if (isCreatingPaymentIntent || !clientSecret) {
9442
9560
  return (jsxRuntime.jsxs("div", { style: {
@@ -9470,7 +9588,7 @@ function PaymentForm({ config, eventDetails, formData, totalAmount, discountCode
9470
9588
  clientSecret,
9471
9589
  appearance: stripeAppearance || { theme: "stripe" },
9472
9590
  locale: config.locale || "de",
9473
- }, children: jsxRuntime.jsx(PaymentFormInner, { config: config, eventDetails: eventDetails, formData: formData, totalAmount: totalAmount, discountCode: discountCode, onSuccess: (result) => {
9591
+ }, children: jsxRuntime.jsx(PaymentFormInner, { config: config, eventDetails: eventDetails, formData: formData, totalAmount: totalAmount, discountCode: discountCode, giftCards: giftCards, onSuccess: (result) => {
9474
9592
  // Clear persisted PI data on successful payment
9475
9593
  clearPersistedPaymentIntent();
9476
9594
  setPaymentIntentId(null);
@@ -9675,6 +9793,222 @@ function Accordion({ title, priceInfo, children, isOpen, onToggle }) {
9675
9793
  }, children: children }))] }));
9676
9794
  }
9677
9795
 
9796
+ // Icons
9797
+ const IconTicket = ({ size = 20, color = "currentColor" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.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" }), jsxRuntime.jsx("path", { d: "M13 5v2" }), jsxRuntime.jsx("path", { d: "M13 17v2" }), jsxRuntime.jsx("path", { d: "M13 11v2" })] }));
9798
+ const IconGift = ({ size = 20, color = "currentColor" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("rect", { x: "3", y: "8", width: "18", height: "4", rx: "1" }), jsxRuntime.jsx("path", { d: "M12 8v13" }), jsxRuntime.jsx("path", { d: "M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7" }), jsxRuntime.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" })] }));
9799
+ const IconCheck = ({ size = 16, color = "currentColor" }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }));
9800
+ const IconX = ({ size = 16, color = "currentColor" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }));
9801
+ const IconSpinner = ({ size = 16, color = "currentColor" }) => (jsxRuntime.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: jsxRuntime.jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }));
9802
+ function VoucherInput({ config, orderValue, eventInstanceId, customerEmail, onVoucherValidated, appliedVouchers, onRemoveVoucher, disabled = false, }) {
9803
+ const [inputValue, setInputValue] = React__default.useState("");
9804
+ const [isLoading, setIsLoading] = React__default.useState(false);
9805
+ const [error, setError] = React__default.useState(null);
9806
+ const [isExpanded, setIsExpanded] = React__default.useState(false);
9807
+ // Check if a discount code is already applied (only one allowed)
9808
+ const hasDiscountCode = appliedVouchers.some((v) => v.type === "discount");
9809
+ const validateVoucher = React__default.useCallback(async (code) => {
9810
+ if (!code.trim()) {
9811
+ setError(null);
9812
+ return;
9813
+ }
9814
+ // Check if code is already applied
9815
+ if (appliedVouchers.some((v) => v.code.toUpperCase() === code.toUpperCase())) {
9816
+ setError("Dieser Code wurde bereits angewendet");
9817
+ return;
9818
+ }
9819
+ setIsLoading(true);
9820
+ setError(null);
9821
+ try {
9822
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/validate-voucher"), {
9823
+ method: "POST",
9824
+ headers: createApiHeaders(config),
9825
+ body: JSON.stringify(createRequestBody(config, {
9826
+ code: code.trim(),
9827
+ orderValue: orderValue,
9828
+ eventInstanceId: eventInstanceId,
9829
+ customerEmail: customerEmail,
9830
+ })),
9831
+ });
9832
+ const data = await response.json();
9833
+ if (data.valid && data.voucher) {
9834
+ // Check if trying to add a second discount code
9835
+ if (data.voucher.type === "discount" && hasDiscountCode) {
9836
+ setError("Es kann nur ein Rabattcode verwendet werden");
9837
+ onVoucherValidated(null, "Es kann nur ein Rabattcode verwendet werden");
9838
+ return;
9839
+ }
9840
+ onVoucherValidated(data.voucher);
9841
+ setInputValue("");
9842
+ setError(null);
9843
+ }
9844
+ else {
9845
+ setError(data.error || "Code nicht gefunden oder ungültig");
9846
+ onVoucherValidated(null, data.error);
9847
+ }
9848
+ }
9849
+ catch (err) {
9850
+ const errorMsg = "Fehler beim Validieren des Codes";
9851
+ setError(errorMsg);
9852
+ onVoucherValidated(null, errorMsg);
9853
+ }
9854
+ finally {
9855
+ setIsLoading(false);
9856
+ }
9857
+ }, [
9858
+ config,
9859
+ orderValue,
9860
+ eventInstanceId,
9861
+ customerEmail,
9862
+ appliedVouchers,
9863
+ hasDiscountCode,
9864
+ onVoucherValidated,
9865
+ ]);
9866
+ const handleSubmit = () => {
9867
+ if (inputValue.trim() && !isLoading && !disabled) {
9868
+ validateVoucher(inputValue);
9869
+ }
9870
+ };
9871
+ const handleKeyDown = (e) => {
9872
+ if (e.key === "Enter") {
9873
+ e.preventDefault();
9874
+ if (inputValue.trim() && !isLoading && !disabled) {
9875
+ validateVoucher(inputValue);
9876
+ }
9877
+ }
9878
+ };
9879
+ const inputStyle = {
9880
+ flex: 1,
9881
+ padding: "10px 12px",
9882
+ backgroundColor: "var(--bw-background-color)",
9883
+ border: "1px solid var(--bw-border-color)",
9884
+ borderRadius: "var(--bw-border-radius)",
9885
+ color: "var(--bw-text-color)",
9886
+ fontSize: "var(--bw-font-size)",
9887
+ fontFamily: "var(--bw-font-family)",
9888
+ outline: "none",
9889
+ transition: "all 0.2s ease",
9890
+ textTransform: "uppercase",
9891
+ };
9892
+ const buttonStyle = {
9893
+ padding: "10px 16px",
9894
+ backgroundColor: "var(--bw-highlight-color)",
9895
+ border: "none",
9896
+ borderRadius: "var(--bw-border-radius)",
9897
+ color: "#fff",
9898
+ fontSize: "var(--bw-font-size)",
9899
+ fontFamily: "var(--bw-font-family)",
9900
+ fontWeight: "600",
9901
+ cursor: disabled || isLoading ? "not-allowed" : "pointer",
9902
+ opacity: disabled || isLoading ? 0.6 : 1,
9903
+ transition: "all 0.2s ease",
9904
+ display: "flex",
9905
+ alignItems: "center",
9906
+ justifyContent: "center",
9907
+ gap: "6px",
9908
+ minWidth: "100px",
9909
+ };
9910
+ const appliedVoucherStyle = {
9911
+ display: "flex",
9912
+ alignItems: "center",
9913
+ justifyContent: "space-between",
9914
+ padding: "10px 12px",
9915
+ backgroundColor: "var(--bw-surface-color)",
9916
+ border: "1px solid var(--bw-border-color)",
9917
+ borderRadius: "var(--bw-border-radius)",
9918
+ marginBottom: "8px",
9919
+ };
9920
+ const removeButtonStyle = {
9921
+ background: "none",
9922
+ border: "none",
9923
+ padding: "4px",
9924
+ cursor: "pointer",
9925
+ color: "var(--bw-error-color)",
9926
+ display: "flex",
9927
+ alignItems: "center",
9928
+ justifyContent: "center",
9929
+ borderRadius: "50%",
9930
+ transition: "background-color 0.2s ease",
9931
+ };
9932
+ return (jsxRuntime.jsxs("div", { style: {
9933
+ backgroundColor: "var(--bw-surface-color)",
9934
+ border: "1px solid var(--bw-border-color)",
9935
+ borderRadius: "var(--bw-border-radius)",
9936
+ overflow: "hidden",
9937
+ }, children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsExpanded(!isExpanded), style: {
9938
+ width: "100%",
9939
+ padding: "var(--bw-spacing)",
9940
+ backgroundColor: "transparent",
9941
+ border: "none",
9942
+ cursor: "pointer",
9943
+ display: "flex",
9944
+ alignItems: "center",
9945
+ justifyContent: "space-between",
9946
+ color: "var(--bw-text-color)",
9947
+ fontFamily: "var(--bw-font-family)",
9948
+ fontSize: "var(--bw-font-size)",
9949
+ fontWeight: "500",
9950
+ }, children: [jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [jsxRuntime.jsx(IconTicket, { size: 18, color: "var(--bw-highlight-color)" }), "Rabattcode oder Gutschein", appliedVouchers.length > 0 && (jsxRuntime.jsx("span", { style: {
9951
+ backgroundColor: "var(--bw-highlight-color)",
9952
+ color: "#fff",
9953
+ padding: "2px 8px",
9954
+ borderRadius: "12px",
9955
+ fontSize: "12px",
9956
+ fontWeight: "600",
9957
+ }, children: appliedVouchers.length }))] }), jsxRuntime.jsx("span", { style: {
9958
+ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
9959
+ transition: "transform 0.2s ease",
9960
+ }, children: "\u25BC" })] }), isExpanded && (jsxRuntime.jsxs("div", { style: { padding: "0 var(--bw-spacing) var(--bw-spacing)" }, children: [appliedVouchers.length > 0 && (jsxRuntime.jsx("div", { style: { marginBottom: "12px" }, children: appliedVouchers.map((voucher) => (jsxRuntime.jsxs("div", { style: appliedVoucherStyle, children: [jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [voucher.type === "discount" ? (jsxRuntime.jsx(IconTicket, { size: 18, color: "var(--bw-success-color)" })) : (jsxRuntime.jsx(IconGift, { size: 18, color: "var(--bw-success-color)" })), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { style: {
9961
+ fontFamily: "var(--bw-font-family)",
9962
+ fontSize: "var(--bw-font-size)",
9963
+ fontWeight: "600",
9964
+ color: "var(--bw-text-color)",
9965
+ display: "flex",
9966
+ alignItems: "center",
9967
+ gap: "6px",
9968
+ }, children: [jsxRuntime.jsx("span", { style: { fontFamily: "monospace" }, children: voucher.code }), jsxRuntime.jsx(IconCheck, { size: 14, color: "var(--bw-success-color)" })] }), jsxRuntime.jsxs("div", { style: {
9969
+ fontFamily: "var(--bw-font-family)",
9970
+ fontSize: "12px",
9971
+ color: "var(--bw-success-color)",
9972
+ }, children: [voucher.type === "discount"
9973
+ ? `−${formatCurrency(voucher.discountAmount)} Rabatt`
9974
+ : `−${formatCurrency(voucher.balanceToUse || voucher.discountAmount)} Gutschein`, voucher.type === "giftCard" &&
9975
+ voucher.remainingBalance !== undefined &&
9976
+ voucher.remainingBalance > 0 && (jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", marginLeft: "8px" }, children: ["(Rest: ", formatCurrency(voucher.remainingBalance), ")"] }))] })] })] }), jsxRuntime.jsx("button", { type: "button", onClick: () => onRemoveVoucher(voucher.code), style: removeButtonStyle, title: "Entfernen", children: jsxRuntime.jsx(IconX, { size: 16 }) })] }, voucher.code))) })), jsxRuntime.jsxs("div", { style: { display: "flex", gap: "8px" }, children: [jsxRuntime.jsx("input", { type: "text", value: inputValue, onChange: (e) => {
9977
+ setInputValue(e.target.value.toUpperCase());
9978
+ setError(null);
9979
+ }, onKeyDown: handleKeyDown, placeholder: hasDiscountCode
9980
+ ? "Gutscheincode eingeben..."
9981
+ : "Rabatt- oder Gutscheincode eingeben...", style: inputStyle, disabled: disabled || isLoading, onFocus: (e) => {
9982
+ e.target.style.borderColor = "var(--bw-highlight-color)";
9983
+ e.target.style.boxShadow = "0 0 0 2px var(--bw-highlight-color)33";
9984
+ }, onBlur: (e) => {
9985
+ e.target.style.borderColor = "var(--bw-border-color)";
9986
+ e.target.style.boxShadow = "none";
9987
+ } }), jsxRuntime.jsx("button", { type: "button", onClick: handleSubmit, style: buttonStyle, disabled: disabled || isLoading || !inputValue.trim(), children: isLoading ? (jsxRuntime.jsx(IconSpinner, { size: 16, color: "#fff" })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(IconCheck, { size: 16 }), "Einl\u00F6sen"] })) })] }), error && (jsxRuntime.jsxs("div", { style: {
9988
+ marginTop: "8px",
9989
+ padding: "8px 12px",
9990
+ backgroundColor: "var(--bw-error-color)15",
9991
+ border: "1px solid var(--bw-error-color)40",
9992
+ borderRadius: "var(--bw-border-radius)",
9993
+ color: "var(--bw-error-color)",
9994
+ fontSize: "var(--bw-font-size)",
9995
+ fontFamily: "var(--bw-font-family)",
9996
+ display: "flex",
9997
+ alignItems: "center",
9998
+ gap: "8px",
9999
+ }, children: [jsxRuntime.jsx(IconX, { size: 16 }), error] })), hasDiscountCode && (jsxRuntime.jsx("div", { style: {
10000
+ marginTop: "8px",
10001
+ fontSize: "12px",
10002
+ color: "var(--bw-text-muted)",
10003
+ fontFamily: "var(--bw-font-family)",
10004
+ }, children: "\uD83D\uDCA1 Es wurde bereits ein Rabattcode angewendet. Du kannst weitere Gutscheine hinzuf\u00FCgen." }))] })), jsxRuntime.jsx("style", { children: `
10005
+ @keyframes spin {
10006
+ from { transform: rotate(0deg); }
10007
+ to { transform: rotate(360deg); }
10008
+ }
10009
+ ` })] }));
10010
+ }
10011
+
9678
10012
  // Form schemas
9679
10013
  const participantSchema = objectType({
9680
10014
  name: stringType().min(1, "Name ist erforderlich"),
@@ -9694,6 +10028,10 @@ const bookingFormSchema = objectType({
9694
10028
  const IconWarning = ({ size = 48, color = "var(--bw-error-color)" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }), jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12", y2: "12" }), jsxRuntime.jsx("circle", { cx: "12", cy: "16", r: "1" })] }));
9695
10029
  const IconMoney = ({ size = 20, color = "var(--bw-text-muted)" }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "4" }), jsxRuntime.jsx("line", { x1: "2", y1: "10", x2: "2", y2: "14" }), jsxRuntime.jsx("line", { x1: "22", y1: "10", x2: "22", y2: "14" })] }));
9696
10030
  function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError, onBackToEventInstances, onBackToEventTypes, selectedEventType, selectedEventInstance, isOpen, onClose, systemConfig, }) {
10031
+ // New voucher system - supports multiple gift cards + one discount code
10032
+ const [appliedVouchers, setAppliedVouchers] = React__default.useState([]);
10033
+ const [voucherError, setVoucherError] = React__default.useState(null);
10034
+ // Legacy state for backward compatibility
9697
10035
  const [discountCode, setDiscountCode] = React__default.useState(null);
9698
10036
  const [discountLoading, setDiscountLoading] = React__default.useState(false);
9699
10037
  const [discountError, setDiscountError] = React__default.useState(null);
@@ -9707,28 +10045,66 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
9707
10045
  },
9708
10046
  });
9709
10047
  const watchedParticipants = form.watch("participants");
9710
- const watchedDiscountCode = form.watch("discountCode");
10048
+ form.watch("discountCode");
9711
10049
  const watchedCustomerName = form.watch("customerName");
9712
10050
  const watchedCustomerEmail = form.watch("customerEmail");
9713
10051
  const customerNameError = form.formState.errors.customerName;
9714
10052
  const customerEmailError = form.formState.errors.customerEmail;
9715
10053
  const watchedAcceptTerms = form.watch("acceptTerms");
9716
- // Calculate total amount and deposit amount
9717
- const calculateTotal = () => {
10054
+ // Calculate base total before any discounts
10055
+ const calculateBaseTotal = React__default.useCallback(() => {
9718
10056
  if (!eventDetails)
9719
10057
  return 0;
9720
- const baseTotal = eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
9721
- return discountCode ? discountCode.newTotal : baseTotal;
9722
- };
10058
+ return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
10059
+ }, [eventDetails, watchedParticipants]);
10060
+ // Calculate total discount from all applied vouchers
10061
+ const calculateTotalDiscount = React__default.useCallback(() => {
10062
+ return appliedVouchers.reduce((total, voucher) => {
10063
+ if (voucher.type === "discount") {
10064
+ return total + voucher.discountAmount;
10065
+ }
10066
+ else if (voucher.type === "giftCard") {
10067
+ return total + (voucher.balanceToUse || voucher.discountAmount);
10068
+ }
10069
+ return total;
10070
+ }, 0);
10071
+ }, [appliedVouchers]);
10072
+ // Calculate total amount after discounts
10073
+ const calculateTotal = React__default.useCallback(() => {
10074
+ const baseTotal = calculateBaseTotal();
10075
+ const totalDiscount = calculateTotalDiscount();
10076
+ return Math.max(0, baseTotal - totalDiscount);
10077
+ }, [calculateBaseTotal, calculateTotalDiscount]);
9723
10078
  const calculateDeposit = () => {
9724
10079
  if (!eventDetails || !eventDetails.deposit)
9725
10080
  return 0;
9726
10081
  const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
9727
10082
  return eventDetails.deposit * participantCount;
9728
10083
  };
10084
+ const baseTotal = calculateBaseTotal();
10085
+ const totalDiscount = calculateTotalDiscount();
9729
10086
  const totalAmount = calculateTotal();
9730
10087
  const depositAmount = calculateDeposit();
9731
- const paymentAmount = depositAmount > 0 ? depositAmount : totalAmount;
10088
+ // If there's a deposit, we pay the deposit; otherwise we pay the total after discounts
10089
+ const paymentAmount = depositAmount > 0 ? Math.max(0, depositAmount - totalDiscount) : totalAmount;
10090
+ // Get discount code for legacy compatibility
10091
+ const appliedDiscountCode = appliedVouchers.find((v) => v.type === "discount");
10092
+ // Get gift cards
10093
+ const appliedGiftCards = appliedVouchers.filter((v) => v.type === "giftCard");
10094
+ // Voucher handlers
10095
+ const handleVoucherValidated = React__default.useCallback((voucher, error) => {
10096
+ if (error) {
10097
+ setVoucherError(error);
10098
+ return;
10099
+ }
10100
+ if (voucher) {
10101
+ setAppliedVouchers((prev) => [...prev, voucher]);
10102
+ setVoucherError(null);
10103
+ }
10104
+ }, []);
10105
+ const handleRemoveVoucher = React__default.useCallback((code) => {
10106
+ setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
10107
+ }, []);
9732
10108
  // Form validation helper
9733
10109
  const isFormValid = () => {
9734
10110
  const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
@@ -9742,48 +10118,51 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
9742
10118
  const hasAcceptedTerms = watchedAcceptTerms === true;
9743
10119
  return validParticipants && participantsWithinLimit && hasName && hasEmail && hasAcceptedTerms;
9744
10120
  };
9745
- // Validate discount codes
10121
+ // Re-validate vouchers when participant count changes (affects order value)
9746
10122
  React__default.useEffect(() => {
9747
- const validateDiscountCode = async (code) => {
9748
- if (!code.trim() || !eventDetails) {
9749
- setDiscountCode(null);
9750
- setDiscountError(null);
9751
- return;
9752
- }
9753
- setDiscountLoading(true);
9754
- setDiscountError(null);
9755
- try {
9756
- const baseTotal = eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
9757
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/validate-discount"), {
9758
- method: "POST",
9759
- headers: createApiHeaders(config),
9760
- body: JSON.stringify(createRequestBody(config, {
9761
- code: code.trim(),
9762
- orderValue: baseTotal,
9763
- })),
9764
- });
9765
- const data = await response.json();
9766
- if (data.valid) {
9767
- setDiscountCode(data.discountCode);
10123
+ // When participants change, we need to recalculate voucher amounts
10124
+ // For now, we'll clear vouchers if the order value changes significantly
10125
+ // In a production app, you might want to re-validate each voucher
10126
+ if (appliedVouchers.length > 0) {
10127
+ // Recalculate discount amounts based on new order value
10128
+ const newBaseTotal = eventDetails?.price
10129
+ ? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
10130
+ : 0;
10131
+ // Update voucher amounts (simplified - in production, re-validate via API)
10132
+ setAppliedVouchers((prev) => prev.map((voucher) => {
10133
+ if (voucher.type === "discount") {
10134
+ let newDiscountAmount = 0;
10135
+ if (voucher.discountType === "percentage") {
10136
+ newDiscountAmount = Math.round((newBaseTotal * (voucher.discountValue || 0)) / 10000);
10137
+ }
10138
+ else {
10139
+ newDiscountAmount = voucher.discountValue || 0;
10140
+ }
10141
+ newDiscountAmount = Math.min(newDiscountAmount, newBaseTotal);
10142
+ return {
10143
+ ...voucher,
10144
+ discountAmount: newDiscountAmount,
10145
+ newTotal: newBaseTotal - newDiscountAmount,
10146
+ };
9768
10147
  }
9769
- else {
9770
- setDiscountCode(null);
9771
- setDiscountError(data.error);
10148
+ else if (voucher.type === "giftCard") {
10149
+ // Gift card balance stays the same, but amount to use might change
10150
+ const remainingAfterDiscount = newBaseTotal -
10151
+ prev
10152
+ .filter((v) => v.type === "discount")
10153
+ .reduce((sum, v) => sum + v.discountAmount, 0);
10154
+ const balanceToUse = Math.min(voucher.currentBalance || 0, Math.max(0, remainingAfterDiscount));
10155
+ return {
10156
+ ...voucher,
10157
+ balanceToUse,
10158
+ remainingBalance: (voucher.currentBalance || 0) - balanceToUse,
10159
+ discountAmount: balanceToUse,
10160
+ };
9772
10161
  }
9773
- }
9774
- catch (err) {
9775
- setDiscountError("Fehler beim Validieren des Rabattcodes");
9776
- setDiscountCode(null);
9777
- }
9778
- finally {
9779
- setDiscountLoading(false);
9780
- }
9781
- };
9782
- const timer = setTimeout(() => {
9783
- validateDiscountCode(watchedDiscountCode || "");
9784
- }, 500);
9785
- return () => clearTimeout(timer);
9786
- }, [watchedDiscountCode, watchedParticipants, eventDetails, config]);
10162
+ return voucher;
10163
+ }));
10164
+ }
10165
+ }, [watchedParticipants, eventDetails]);
9787
10166
  // Helper functions
9788
10167
  const addParticipant = () => {
9789
10168
  const currentParticipants = form.getValues("participants");
@@ -9938,7 +10317,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
9938
10317
  color: "var(--bw-text-color)",
9939
10318
  fontWeight: "500",
9940
10319
  fontFamily: "var(--bw-font-family)",
9941
- }, children: [formatCurrency(eventDetails.price), " pro Person"] })] })] })] }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxRuntime.jsxs("form", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxRuntime.jsxs("div", { style: {
10320
+ }, children: [formatCurrency(eventDetails.price), " pro Person"] })] })] })] }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "var(--bw-spacing-large)" }, children: [jsxRuntime.jsxs("div", { style: {
9942
10321
  backgroundColor: "var(--bw-surface-color)",
9943
10322
  border: `1px solid var(--bw-border-color)`,
9944
10323
  backdropFilter: "blur(4px)",
@@ -10083,7 +10462,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
10083
10462
  color: "var(--bw-error-color)",
10084
10463
  fontSize: "var(--bw-font-size)",
10085
10464
  fontFamily: "var(--bw-font-family)",
10086
- }, children: ["Maximal ", eventDetails.availableSpots, " Pl\u00E4tze verf\u00FCgbar."] }))] })] }), jsxRuntime.jsxs("div", { style: {
10465
+ }, children: ["Maximal ", eventDetails.availableSpots, " Pl\u00E4tze verf\u00FCgbar."] }))] })] }), jsxRuntime.jsx(VoucherInput, { config: config, orderValue: baseTotal, eventInstanceId: eventDetails?.id, customerEmail: watchedCustomerEmail, onVoucherValidated: handleVoucherValidated, appliedVouchers: appliedVouchers, onRemoveVoucher: handleRemoveVoucher, disabled: !eventDetails }), jsxRuntime.jsxs("div", { style: {
10087
10466
  backgroundColor: "var(--bw-surface-color)",
10088
10467
  border: `1px solid var(--bw-border-color)`,
10089
10468
  backdropFilter: "blur(4px)",
@@ -10182,7 +10561,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
10182
10561
  color: "var(--bw-text-color)",
10183
10562
  fontWeight: "500",
10184
10563
  fontFamily: "var(--bw-font-family)",
10185
- }, children: formatCurrency(eventDetails.deposit || 0) })] })), discountCode && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: {
10564
+ }, children: formatCurrency(eventDetails.deposit || 0) })] })), appliedVouchers.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: {
10186
10565
  display: "flex",
10187
10566
  justifyContent: "space-between",
10188
10567
  alignItems: "center",
@@ -10191,20 +10570,31 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
10191
10570
  fontFamily: "var(--bw-font-family)",
10192
10571
  }, children: "Zwischensumme:" }), jsxRuntime.jsx("span", { style: {
10193
10572
  color: "var(--bw-text-muted)",
10194
- textDecoration: "line-through",
10573
+ textDecoration: totalDiscount > 0 ? "line-through" : "none",
10195
10574
  fontFamily: "var(--bw-font-family)",
10196
- }, children: formatCurrency(eventDetails.price *
10197
- watchedParticipants.filter((p) => p.name.trim()).length) })] }), jsxRuntime.jsxs("div", { style: {
10575
+ }, children: formatCurrency(baseTotal) })] }), appliedDiscountCode && (jsxRuntime.jsxs("div", { style: {
10198
10576
  display: "flex",
10199
10577
  justifyContent: "space-between",
10200
10578
  alignItems: "center",
10201
- }, children: [jsxRuntime.jsx("span", { style: {
10579
+ }, children: [jsxRuntime.jsxs("span", { style: {
10202
10580
  color: "var(--bw-success-color)",
10203
10581
  fontFamily: "var(--bw-font-family)",
10204
- }, children: "Rabatt:" }), jsxRuntime.jsxs("span", { style: {
10582
+ fontSize: "var(--bw-font-size)",
10583
+ }, children: ["Rabatt (", appliedDiscountCode.code, "):"] }), jsxRuntime.jsxs("span", { style: {
10205
10584
  color: "var(--bw-success-color)",
10206
10585
  fontFamily: "var(--bw-font-family)",
10207
- }, children: ["-", formatCurrency(discountCode.discountAmount)] })] })] })), jsxRuntime.jsxs("div", { style: {
10586
+ }, children: ["-", formatCurrency(appliedDiscountCode.discountAmount)] })] })), appliedGiftCards.map((giftCard) => (jsxRuntime.jsxs("div", { style: {
10587
+ display: "flex",
10588
+ justifyContent: "space-between",
10589
+ alignItems: "center",
10590
+ }, children: [jsxRuntime.jsxs("span", { style: {
10591
+ color: "var(--bw-success-color)",
10592
+ fontFamily: "var(--bw-font-family)",
10593
+ fontSize: "var(--bw-font-size)",
10594
+ }, children: ["Gutschein (", giftCard.code, "):"] }), jsxRuntime.jsxs("span", { style: {
10595
+ color: "var(--bw-success-color)",
10596
+ fontFamily: "var(--bw-font-family)",
10597
+ }, children: ["-", formatCurrency(giftCard.balanceToUse || giftCard.discountAmount)] })] }, giftCard.code)))] })), jsxRuntime.jsxs("div", { style: {
10208
10598
  borderTop: `1px solid var(--bw-border-color)`,
10209
10599
  paddingTop: "12px",
10210
10600
  }, children: [depositAmount > 0 && (jsxRuntime.jsxs("div", { style: {
@@ -10277,7 +10667,15 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
10277
10667
  fontFamily: "var(--bw-font-family)",
10278
10668
  borderBottom: "2px solid var(--bw-highlight-color)",
10279
10669
  paddingBottom: 4,
10280
- }, children: "Zahlung" }), jsxRuntime.jsx(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: discountCode, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, stripePromise: stripePromise, stripeAppearance: stripeAppearance })] }));
10670
+ }, children: "Zahlung" }), jsxRuntime.jsx(PaymentForm, { config: config, eventDetails: eventDetails, formData: form.getValues(), totalAmount: paymentAmount, discountCode: appliedDiscountCode ? {
10671
+ id: appliedDiscountCode.id,
10672
+ code: appliedDiscountCode.code,
10673
+ description: appliedDiscountCode.description || undefined,
10674
+ type: appliedDiscountCode.discountType || "percentage",
10675
+ value: appliedDiscountCode.discountValue || 0,
10676
+ discountAmount: appliedDiscountCode.discountAmount,
10677
+ newTotal: appliedDiscountCode.newTotal,
10678
+ } : null, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, stripePromise: stripePromise, stripeAppearance: stripeAppearance })] }));
10281
10679
  })()] })] }), jsxRuntime.jsx("style", { children: `
10282
10680
  .booking-widget-container *,
10283
10681
  .booking-widget-container *::before,
@@ -11304,137 +11702,155 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
11304
11702
  font-size: 1.1rem !important;
11305
11703
  }
11306
11704
  }
11307
- ` }), jsxRuntime.jsxs(Sidebar, { isOpen: isOpen, onClose: handleClose, title: "Termin-Auswahl", children: [jsxRuntime.jsx("p", { className: "bw-event-instance-title", children: selectedEventType?.name }), jsxRuntime.jsx("div", { className: "bw-event-instance-list", style: { padding: "24px" }, children: jsxRuntime.jsx("div", { style: {
11308
- display: "flex",
11309
- flexDirection: "column",
11310
- gap: "20px",
11311
- }, children: monthYearGroups.map(({ key, label, events, minPrice }) => {
11312
- const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
11313
- return (jsxRuntime.jsx(Accordion, { title: label, priceInfo: jsxRuntime.jsx("div", { style: {
11314
- fontSize: "1rem",
11315
- backgroundColor: monthPriceDisplayInfo
11316
- ? monthPriceDisplayInfo.backgroundColor
11317
- : "#14532d",
11318
- color: monthPriceDisplayInfo ? monthPriceDisplayInfo.textColor : "#4ade80",
11319
- fontWeight: 500,
11320
- marginLeft: "auto",
11321
- padding: "4px 8px",
11322
- borderRadius: "var(--bw-border-radius-small)",
11323
- border: monthPriceDisplayInfo ? "none" : undefined,
11324
- boxShadow: monthPriceDisplayInfo
11325
- ? "0 2px 4px rgba(0, 0, 0, 0.2)"
11326
- : undefined,
11327
- }, children: `ab ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsxRuntime.jsx("div", { style: {
11328
- display: "flex",
11329
- flexDirection: "column",
11330
- gap: "12px",
11331
- paddingTop: "12px",
11332
- }, children: events.map((event) => {
11333
- const availableSpots = event.maxParticipants - event.participantCount;
11334
- const isFullyBooked = availableSpots === 0;
11335
- const startDate = new Date(event.startTime);
11336
- const isPastEvent = today.toISOString() >= startDate.toISOString();
11337
- return (jsxRuntime.jsxs("div", { className: "bw-event-instance-card", style: {
11338
- position: "relative",
11339
- cursor: !isFullyBooked && !isPastEvent && event.bookingOpen
11340
- ? "pointer"
11341
- : "not-allowed",
11342
- border: "1px solid var(--bw-border-color)",
11343
- backgroundColor: "var(--bw-surface-color)",
11344
- borderRadius: "var(--bw-border-radius)",
11345
- padding: "16px 20px",
11346
- transition: "all 0.2s ease",
11347
- opacity: isFullyBooked || isPastEvent ? 0.3 : 1,
11348
- filter: isFullyBooked || isPastEvent ? "grayscale(40%)" : "none",
11349
- fontFamily: "var(--bw-font-family)",
11350
- }, onClick: () => {
11351
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11352
- handleEventInstanceSelect(event);
11353
- }
11354
- }, onMouseEnter: (e) => {
11355
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11356
- e.currentTarget.style.transform = "scale(1.02)";
11357
- e.currentTarget.style.backgroundColor =
11358
- "var(--bw-surface-muted, rgba(59, 130, 246, 0.1))";
11359
- }
11360
- }, onMouseLeave: (e) => {
11361
- if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11362
- e.currentTarget.style.transform = "scale(1)";
11363
- e.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
11364
- }
11365
- }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
11366
- position: "absolute",
11367
- top: 0,
11368
- left: 0,
11369
- width: "100%",
11370
- height: "100%",
11371
- backgroundColor: "var(--bw-overlay-color, rgba(15, 23, 42, 0.8))",
11372
- borderRadius: "var(--bw-border-radius)",
11373
- display: "flex",
11374
- alignItems: "center",
11375
- justifyContent: "center",
11376
- }, children: jsxRuntime.jsx("div", { style: {
11377
- width: "32px",
11378
- height: "32px",
11379
- color: "var(--bw-highlight-color-muted, rgba(59, 130, 246, 0.8))",
11380
- animation: "spin 1s linear infinite",
11381
- fontSize: "32px",
11382
- }, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants }), jsxRuntime.jsxs("div", { style: {
11383
- display: "flex",
11384
- justifyContent: "space-between",
11385
- width: "100%",
11386
- alignItems: "start",
11387
- gap: "12px",
11388
- marginBottom: "4px",
11389
- }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "start", gap: "12px" }, children: [jsxRuntime.jsx("div", { className: "bw-event-instance-datebox", style: {
11390
- fontSize: "var(--bw-font-size)",
11391
- transition: "all 0.2s ease",
11392
- borderRadius: "var(--bw-border-radius-small)",
11393
- borderTop: `4px solid var(--bw-border-color)`,
11394
- border: "1px solid var(--bw-border-color)",
11395
- width: "40px",
11396
- height: "40px",
11397
- display: "flex",
11398
- alignItems: "center",
11399
- justifyContent: "center",
11400
- fontWeight: "bold",
11401
- color: "var(--bw-text-color)",
11402
- backgroundColor: "var(--bw-background-color)",
11403
- }, children: startDate.getDate() }), jsxRuntime.jsxs("div", { style: {
11404
- fontSize: "var(--bw-font-size)",
11405
- color: "var(--bw-text-color)",
11406
- display: "flex",
11407
- flexDirection: "column",
11408
- alignItems: "start",
11409
- justifyContent: "start",
11410
- lineHeight: "1.2",
11411
- }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.startTime) }), formatWeekday(event.startTime) !==
11412
- formatWeekday(event.endTime) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsxRuntime.jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.endTime) })] }))] }), jsxRuntime.jsx("div", { children: formatWeekday(event.startTime) ===
11413
- formatWeekday(event.endTime) ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime) }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsxRuntime.jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime) })] })) : (jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime), " Uhr"] })) })] }), jsxRuntime.jsxs("span", { style: {
11414
- fontSize: "12px",
11415
- fontWeight: 400,
11416
- color: "var(--bw-text-muted)",
11417
- marginLeft: "6px",
11418
- background: "var(--bw-background-muted)",
11419
- whiteSpace: "nowrap",
11420
- }, children: [event.durationDays, " Tag", event.durationDays > 1 ? "e" : ""] })] }), jsxRuntime.jsx("div", { className: "bw-event-instance-price", style: {
11421
- textAlign: "right",
11422
- display: "flex",
11423
- flexDirection: "column",
11424
- alignItems: "end",
11425
- }, children: jsxRuntime.jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices }) })] }), event.name !== selectedEventType?.name && (jsxRuntime.jsx("h4", { className: "bw-event-instance-title", style: {
11426
- fontSize: "var(--bw-font-size)",
11427
- fontWeight: "600",
11428
- color: "var(--bw-text-color)",
11429
- lineHeight: "1.25",
11430
- margin: "0 0 2px 0",
11431
- display: "flex",
11432
- alignItems: "center",
11433
- gap: "8px",
11434
- maxWidth: "230px",
11435
- }, children: event.name }))] }, event.id));
11436
- }) }) }, key));
11437
- }) }) })] })] }));
11705
+ ` }), jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, children: jsxRuntime.jsx("div", { className: "bw-event-instance-list", style: { padding: "24px" }, children: jsxRuntime.jsx("div", { style: {
11706
+ display: "flex",
11707
+ flexDirection: "column",
11708
+ gap: "20px",
11709
+ }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
11710
+ const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
11711
+ return (jsxRuntime.jsxs(React__default.Fragment, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsxRuntime.jsx("div", { style: {
11712
+ height: 1,
11713
+ backgroundColor: "var(--bw-border-color)",
11714
+ margin: "4px 0",
11715
+ } })), jsxRuntime.jsx(Accordion, { title: label, priceInfo: jsxRuntime.jsx("div", { style: {
11716
+ fontSize: "1rem",
11717
+ backgroundColor: monthPriceDisplayInfo
11718
+ ? monthPriceDisplayInfo.backgroundColor
11719
+ : "#14532d",
11720
+ color: monthPriceDisplayInfo
11721
+ ? monthPriceDisplayInfo.textColor
11722
+ : "#4ade80",
11723
+ fontWeight: 500,
11724
+ marginLeft: "auto",
11725
+ padding: "4px 8px",
11726
+ borderRadius: "var(--bw-border-radius-small)",
11727
+ border: monthPriceDisplayInfo ? "none" : undefined,
11728
+ boxShadow: monthPriceDisplayInfo
11729
+ ? "0 2px 4px rgba(0, 0, 0, 0.2)"
11730
+ : undefined,
11731
+ }, children: `ab ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsxRuntime.jsx("div", { style: {
11732
+ display: "flex",
11733
+ flexDirection: "column",
11734
+ gap: "12px",
11735
+ paddingTop: "12px",
11736
+ }, children: events.map((event) => {
11737
+ const availableSpots = event.maxParticipants - event.participantCount;
11738
+ const isFullyBooked = availableSpots === 0;
11739
+ const startDate = new Date(event.startTime);
11740
+ const isPastEvent = today.toISOString() >= startDate.toISOString();
11741
+ return (jsxRuntime.jsxs("div", { className: "bw-event-instance-card", style: {
11742
+ position: "relative",
11743
+ cursor: !isFullyBooked && !isPastEvent && event.bookingOpen
11744
+ ? "pointer"
11745
+ : "not-allowed",
11746
+ border: "1px solid var(--bw-border-color)",
11747
+ backgroundColor: "var(--bw-surface-color)",
11748
+ borderRadius: "var(--bw-border-radius)",
11749
+ padding: "16px 20px",
11750
+ transition: "all 0.2s ease",
11751
+ opacity: isFullyBooked || isPastEvent ? 0.3 : 1,
11752
+ filter: isFullyBooked || isPastEvent ? "grayscale(40%)" : "none",
11753
+ fontFamily: "var(--bw-font-family)",
11754
+ }, onClick: () => {
11755
+ if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11756
+ handleEventInstanceSelect(event);
11757
+ }
11758
+ }, onMouseEnter: (e) => {
11759
+ if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11760
+ e.currentTarget.style.transform = "scale(1.02)";
11761
+ e.currentTarget.style.backgroundColor =
11762
+ "var(--bw-surface-muted, rgba(59, 130, 246, 0.1))";
11763
+ }
11764
+ }, onMouseLeave: (e) => {
11765
+ if (!isFullyBooked && !isPastEvent && event.bookingOpen) {
11766
+ e.currentTarget.style.transform = "scale(1)";
11767
+ e.currentTarget.style.backgroundColor = "var(--bw-surface-color)";
11768
+ }
11769
+ }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
11770
+ position: "absolute",
11771
+ top: 0,
11772
+ left: 0,
11773
+ width: "100%",
11774
+ height: "100%",
11775
+ backgroundColor: "var(--bw-overlay-color, rgba(15, 23, 42, 0.8))",
11776
+ borderRadius: "var(--bw-border-radius)",
11777
+ display: "flex",
11778
+ alignItems: "center",
11779
+ justifyContent: "center",
11780
+ }, children: jsxRuntime.jsx("div", { style: {
11781
+ width: "32px",
11782
+ height: "32px",
11783
+ color: "var(--bw-highlight-color-muted, rgba(59, 130, 246, 0.8))",
11784
+ animation: "spin 1s linear infinite",
11785
+ fontSize: "32px",
11786
+ }, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants }), jsxRuntime.jsxs("div", { style: {
11787
+ display: "flex",
11788
+ justifyContent: "space-between",
11789
+ width: "100%",
11790
+ alignItems: "start",
11791
+ gap: "12px",
11792
+ marginBottom: "4px",
11793
+ }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "start", gap: "12px" }, children: [jsxRuntime.jsx("div", { className: "bw-event-instance-datebox", style: {
11794
+ fontSize: "var(--bw-font-size)",
11795
+ transition: "all 0.2s ease",
11796
+ borderRadius: "var(--bw-border-radius-small)",
11797
+ borderTop: `4px solid var(--bw-border-color)`,
11798
+ border: "1px solid var(--bw-border-color)",
11799
+ width: "40px",
11800
+ height: "40px",
11801
+ display: "flex",
11802
+ alignItems: "center",
11803
+ justifyContent: "center",
11804
+ fontWeight: "bold",
11805
+ color: "var(--bw-text-color)",
11806
+ backgroundColor: "var(--bw-background-color)",
11807
+ }, children: startDate.getDate() }), jsxRuntime.jsxs("div", { style: {
11808
+ fontSize: "var(--bw-font-size)",
11809
+ color: "var(--bw-text-color)",
11810
+ display: "flex",
11811
+ flexDirection: "column",
11812
+ alignItems: "start",
11813
+ justifyContent: "start",
11814
+ lineHeight: "1.2",
11815
+ }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.startTime) }), formatWeekday(event.startTime) !==
11816
+ formatWeekday(event.endTime) && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: {
11817
+ color: "var(--bw-text-muted)",
11818
+ fontSize: "14px",
11819
+ }, children: " - " }), jsxRuntime.jsx("span", { className: "bw-event-instance-title", style: { fontWeight: "600", marginBottom: "2px" }, children: formatWeekday(event.endTime) })] }))] }), jsxRuntime.jsx("div", { children: formatWeekday(event.startTime) ===
11820
+ formatWeekday(event.endTime) ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { style: {
11821
+ color: "var(--bw-text-muted)",
11822
+ fontSize: "14px",
11823
+ }, children: formatTime(event.startTime) }), jsxRuntime.jsx("span", { style: {
11824
+ color: "var(--bw-text-muted)",
11825
+ fontSize: "14px",
11826
+ }, children: " - " }), jsxRuntime.jsx("span", { style: {
11827
+ color: "var(--bw-text-muted)",
11828
+ fontSize: "14px",
11829
+ }, children: formatTime(event.endTime) })] })) : (jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime), " Uhr"] })) })] }), jsxRuntime.jsxs("span", { style: {
11830
+ fontSize: "12px",
11831
+ fontWeight: 400,
11832
+ color: "var(--bw-text-muted)",
11833
+ marginLeft: "6px",
11834
+ background: "var(--bw-background-muted)",
11835
+ whiteSpace: "nowrap",
11836
+ }, children: [event.durationDays, " Tag", event.durationDays > 1 ? "e" : ""] })] }), jsxRuntime.jsx("div", { className: "bw-event-instance-price", style: {
11837
+ textAlign: "right",
11838
+ display: "flex",
11839
+ flexDirection: "column",
11840
+ alignItems: "end",
11841
+ }, children: jsxRuntime.jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices }) })] }), event.name !== selectedEventType?.name && (jsxRuntime.jsx("h4", { className: "bw-event-instance-title", style: {
11842
+ fontSize: "var(--bw-font-size)",
11843
+ fontWeight: "600",
11844
+ color: "var(--bw-text-color)",
11845
+ lineHeight: "1.25",
11846
+ margin: "0 0 2px 0",
11847
+ display: "flex",
11848
+ alignItems: "center",
11849
+ gap: "8px",
11850
+ maxWidth: "230px",
11851
+ }, children: event.name }))] }, event.id));
11852
+ }) }) })] }, key));
11853
+ }) }) }) })] }));
11438
11854
  }
11439
11855
 
11440
11856
  // Loading skeleton component for NextEventsPreview