@bigz-app/booking-widget 1.4.5 → 1.5.1

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.
Files changed (32) hide show
  1. package/dist/booking-widget.js +625 -74
  2. package/dist/booking-widget.js.map +1 -1
  3. package/dist/components/UniversalBookingWidget.d.ts +18 -0
  4. package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
  5. package/dist/components/booking/MolliePaymentForm.d.ts +11 -1
  6. package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
  7. package/dist/components/booking/StripePaymentForm.d.ts +11 -1
  8. package/dist/components/booking/StripePaymentForm.d.ts.map +1 -1
  9. package/dist/components/events/EventTypeSelection.d.ts +15 -0
  10. package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
  11. package/dist/components/events/FreeformSelection.d.ts +49 -0
  12. package/dist/components/events/FreeformSelection.d.ts.map +1 -0
  13. package/dist/components/events/index.d.ts +1 -0
  14. package/dist/components/events/index.d.ts.map +1 -1
  15. package/dist/components/voucher/VoucherIntegration.d.ts +3 -2
  16. package/dist/components/voucher/VoucherIntegration.d.ts.map +1 -1
  17. package/dist/components/voucher/VoucherPurchaseForm.d.ts +8 -2
  18. package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
  19. package/dist/components/voucher/index.d.ts +1 -1
  20. package/dist/components/voucher/index.d.ts.map +1 -1
  21. package/dist/components/voucher/useVoucherConfig.d.ts +6 -0
  22. package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
  23. package/dist/i18n/locales/de.d.ts.map +1 -1
  24. package/dist/i18n/locales/en.d.ts.map +1 -1
  25. package/dist/i18n/locales/es.d.ts.map +1 -1
  26. package/dist/i18n/locales/pt.d.ts.map +1 -1
  27. package/dist/i18n/locales/sv.d.ts.map +1 -1
  28. package/dist/index.cjs +625 -74
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.esm.js +625 -74
  31. package/dist/index.esm.js.map +1 -1
  32. package/package.json +1 -1
@@ -207,6 +207,10 @@
207
207
  "booking.stepUpsells": "Extras",
208
208
  "booking.stepDetails": "Angaben",
209
209
  "booking.stepPayment": "Zahlung",
210
+ "booking.steps": "{{count}} Schritt(e)",
211
+ "booking.freeformUnitsAvailable": "{{count}} Einheiten verfuegbar",
212
+ "booking.freeformNotAvailable": "Das gewaehlte Zeitfenster ist nicht verfuegbar",
213
+ "booking.freeformMissingConfig": "Die Freeform-Konfiguration fuer diesen Event-Typ fehlt.",
210
214
  // Booking summary
211
215
  "summary.title": "Buchungszusammenfassung",
212
216
  "summary.subtotal": "Zwischensumme:",
@@ -262,6 +266,13 @@
262
266
  "voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
263
267
  "voucher.eventType": "Erlebnisgutschein",
264
268
  "voucher.eventTypeDesc": "Für ein bestimmtes Erlebnis",
269
+ "voucher.categoryType": "Kategoriegutschein",
270
+ "voucher.categoryTypeDesc": "Für ein beliebiges Erlebnis einer Kategorie",
271
+ "voucher.categoryVoucher": "Kategoriegutschein",
272
+ "voucher.selectCategory": "Kategorie wählen",
273
+ "voucher.categoryLabel": "Kategorie",
274
+ "voucher.selectCategoryPlaceholder": "Kategorie auswählen...",
275
+ "voucher.categoryAmount": "Gutscheinwert",
265
276
  "voucher.selectAmount": "Betrag wählen",
266
277
  "voucher.customAmount": "Oder eigenen Betrag eingeben",
267
278
  "voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
@@ -515,6 +526,10 @@
515
526
  "booking.stepUpsells": "Extras",
516
527
  "booking.stepDetails": "Details",
517
528
  "booking.stepPayment": "Payment",
529
+ "booking.steps": "{{count}} step(s)",
530
+ "booking.freeformUnitsAvailable": "{{count}} units available",
531
+ "booking.freeformNotAvailable": "Selected window is not available",
532
+ "booking.freeformMissingConfig": "Missing freeform configuration for this event type.",
518
533
  // Booking summary
519
534
  "summary.title": "Booking Summary",
520
535
  "summary.subtotal": "Subtotal:",
@@ -570,6 +585,13 @@
570
585
  "voucher.monetaryTypeDesc": "Choose any amount",
571
586
  "voucher.eventType": "Event Voucher",
572
587
  "voucher.eventTypeDesc": "For a specific experience",
588
+ "voucher.categoryType": "Category Voucher",
589
+ "voucher.categoryTypeDesc": "For any experience in a category",
590
+ "voucher.categoryVoucher": "Category Voucher",
591
+ "voucher.selectCategory": "Select Category",
592
+ "voucher.categoryLabel": "Category",
593
+ "voucher.selectCategoryPlaceholder": "Choose a category...",
594
+ "voucher.categoryAmount": "Voucher Value",
573
595
  "voucher.selectAmount": "Select Amount",
574
596
  "voucher.customAmount": "Or enter custom amount",
575
597
  "voucher.amountRange": "Amount must be between {{min}} and {{max}}",
@@ -860,6 +882,13 @@
860
882
  "voucher.monetaryTypeDesc": "Elige cualquier cantidad",
861
883
  "voucher.eventType": "Vale de experiencia",
862
884
  "voucher.eventTypeDesc": "Para una experiencia específica",
885
+ "voucher.categoryType": "Vale de categoría",
886
+ "voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
887
+ "voucher.categoryVoucher": "Vale de categoría",
888
+ "voucher.selectCategory": "Seleccionar categoría",
889
+ "voucher.categoryLabel": "Categoría",
890
+ "voucher.selectCategoryPlaceholder": "Elige una categoría...",
891
+ "voucher.categoryAmount": "Valor del vale",
863
892
  "voucher.selectAmount": "Selecciona el importe",
864
893
  "voucher.customAmount": "O introduce un importe personalizado",
865
894
  "voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
@@ -1150,6 +1179,13 @@
1150
1179
  "voucher.monetaryTypeDesc": "Escolha qualquer quantia",
1151
1180
  "voucher.eventType": "Vale de experiência",
1152
1181
  "voucher.eventTypeDesc": "Para uma experiência específica",
1182
+ "voucher.categoryType": "Vale de categoria",
1183
+ "voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
1184
+ "voucher.categoryVoucher": "Vale de categoria",
1185
+ "voucher.selectCategory": "Selecionar categoria",
1186
+ "voucher.categoryLabel": "Categoria",
1187
+ "voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
1188
+ "voucher.categoryAmount": "Valor do vale",
1153
1189
  "voucher.selectAmount": "Selecione o valor",
1154
1190
  "voucher.customAmount": "Ou introduza um valor personalizado",
1155
1191
  "voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
@@ -1440,6 +1476,13 @@
1440
1476
  "voucher.monetaryTypeDesc": "Välj valfritt belopp",
1441
1477
  "voucher.eventType": "Upplevelsebevis",
1442
1478
  "voucher.eventTypeDesc": "För en specifik upplevelse",
1479
+ "voucher.categoryType": "Kategoripresent",
1480
+ "voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
1481
+ "voucher.categoryVoucher": "Kategoripresent",
1482
+ "voucher.selectCategory": "Välj kategori",
1483
+ "voucher.categoryLabel": "Kategori",
1484
+ "voucher.selectCategoryPlaceholder": "Välj en kategori...",
1485
+ "voucher.categoryAmount": "Presentkortsvärde",
1443
1486
  "voucher.selectAmount": "Välj belopp",
1444
1487
  "voucher.customAmount": "Eller ange eget belopp",
1445
1488
  "voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
@@ -6478,7 +6521,7 @@
6478
6521
  document.head.appendChild(script);
6479
6522
  });
6480
6523
  }
6481
- function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
6524
+ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
6482
6525
  const t = useTranslations();
6483
6526
  const { locale } = useLocale();
6484
6527
  const [isLoading, setIsLoading] = d$1(false);
@@ -6492,7 +6535,9 @@
6492
6535
  const cardFormRef = A$2(null);
6493
6536
  const cardContainerRef = A$2(null);
6494
6537
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6495
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6538
+ const isFullyCoveredByGiftCards = freeformSelection
6539
+ ? false
6540
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6496
6541
  const isCreditCard = selectedMethod === "creditcard";
6497
6542
  y$1(() => {
6498
6543
  if (isFullyCoveredByGiftCards)
@@ -6602,6 +6647,9 @@
6602
6647
  return (u$2(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
6603
6648
  }
6604
6649
  const validateBeforePayment = () => {
6650
+ if (freeformSelection) {
6651
+ return null;
6652
+ }
6605
6653
  const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
6606
6654
  const availableSpots = eventDetails.availableSpots || 0;
6607
6655
  if (participantCount > availableSpots) {
@@ -6613,21 +6661,41 @@
6613
6661
  return null;
6614
6662
  };
6615
6663
  const buildRequestData = (cardToken) => ({
6616
- eventInstanceId: config.eventInstanceId || eventDetails.id,
6617
- organizationId: config.organizationId,
6618
- amount: Math.round(totalAmount),
6619
- currency: "EUR",
6620
- participants: formData.participants.filter((p) => p.name?.trim()),
6621
- discountCode: discountCode?.code,
6622
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6623
- customerName: formData.customerName?.trim(),
6624
- customerEmail: formData.customerEmail?.trim(),
6625
- customerPhone: formData.customerPhone?.trim(),
6626
- comment: formData.comment?.trim(),
6627
- ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6628
- ...(cardToken && { cardToken }),
6629
- ...(selectedMethod && !cardToken && { method: selectedMethod }),
6630
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6664
+ ...(freeformSelection
6665
+ ? {
6666
+ eventTypeId: freeformSelection.eventTypeId,
6667
+ organizationId: config.organizationId,
6668
+ start: freeformSelection.start,
6669
+ end: freeformSelection.end,
6670
+ requestedUnits: freeformSelection.requestedUnits,
6671
+ amount: Math.round(totalAmount),
6672
+ currency: "EUR",
6673
+ customerName: freeformSelection.customerName.trim(),
6674
+ customerEmail: freeformSelection.customerEmail.trim(),
6675
+ customerPhone: freeformSelection.customerPhone?.trim(),
6676
+ comment: freeformSelection.comment?.trim(),
6677
+ locale,
6678
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6679
+ ...(cardToken && { cardToken }),
6680
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6681
+ }
6682
+ : {
6683
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
6684
+ organizationId: config.organizationId,
6685
+ amount: Math.round(totalAmount),
6686
+ currency: "EUR",
6687
+ participants: formData.participants.filter((p) => p.name?.trim()),
6688
+ discountCode: discountCode?.code,
6689
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6690
+ customerName: formData.customerName?.trim(),
6691
+ customerEmail: formData.customerEmail?.trim(),
6692
+ customerPhone: formData.customerPhone?.trim(),
6693
+ comment: formData.comment?.trim(),
6694
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6695
+ ...(cardToken && { cardToken }),
6696
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6697
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6698
+ }),
6631
6699
  });
6632
6700
  const handleCardPayment = async () => {
6633
6701
  if (!mollieRef.current)
@@ -6645,7 +6713,9 @@
6645
6713
  setPaymentError(tokenError?.message || t("error.createPayment"));
6646
6714
  return;
6647
6715
  }
6648
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6716
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6717
+ ? "/booking/create-freeform-mollie-payment"
6718
+ : "/booking/create-mollie-payment"), {
6649
6719
  method: "POST",
6650
6720
  headers: createApiHeaders(config, locale),
6651
6721
  body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
@@ -6681,7 +6751,9 @@
6681
6751
  setPaymentError(validationError);
6682
6752
  return;
6683
6753
  }
6684
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6754
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6755
+ ? "/booking/create-freeform-mollie-payment"
6756
+ : "/booking/create-mollie-payment"), {
6685
6757
  method: "POST",
6686
6758
  headers: createApiHeaders(config, locale),
6687
6759
  body: JSON.stringify(createRequestBody(config, buildRequestData())),
@@ -6709,7 +6781,7 @@
6709
6781
  ? t("button.processingPayment")
6710
6782
  : selectedMethodData
6711
6783
  ? t("payment.payWithMethod", { method: selectedMethodData.description })
6712
- : totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6784
+ : !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6713
6785
  ? t("button.depositAndBook")
6714
6786
  : t("button.bookNow");
6715
6787
  const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
@@ -7016,12 +7088,18 @@
7016
7088
  };
7017
7089
 
7018
7090
  // Inner component that uses the Stripe hooks
7019
- function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
7091
+ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
7020
7092
  const t = useTranslations();
7021
- const participantCount = formData.participants.filter((p) => p.name.trim()).length;
7022
- const fullAmount = eventDetails.price * participantCount;
7023
- const submitLabel = totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
7093
+ const isFreeform = Boolean(freeformSelection);
7094
+ const participantCount = isFreeform
7095
+ ? 1
7096
+ : formData.participants.filter((p) => p.name.trim()).length;
7097
+ const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
7098
+ const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
7024
7099
  return (u$2(StripeElementsPaymentForm, { onValidate: () => {
7100
+ if (isFreeform) {
7101
+ return null;
7102
+ }
7025
7103
  const availableSpots = eventDetails.availableSpots || 0;
7026
7104
  if (participantCount > availableSpots) {
7027
7105
  return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
@@ -7043,7 +7121,7 @@
7043
7121
  },
7044
7122
  }, submitContent: u$2(k$3, { children: submitLabel }), loadingContent: u$2(k$3, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
7045
7123
  }
7046
- function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
7124
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
7047
7125
  const t = useTranslations();
7048
7126
  const { locale } = useLocale();
7049
7127
  const [clientSecret, setClientSecret] = d$1(null);
@@ -7063,7 +7141,9 @@
7063
7141
  return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
7064
7142
  }, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
7065
7143
  const storageKey = typeof window !== "undefined"
7066
- ? `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
7144
+ ? freeformSelection
7145
+ ? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
7146
+ : `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
7067
7147
  : "";
7068
7148
  const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
7069
7149
  function loadPersistedPaymentIntent() {
@@ -7118,48 +7198,82 @@
7118
7198
  }, [paymentIntentId]);
7119
7199
  y$1(() => {
7120
7200
  const createStripePayment = async () => {
7121
- if (!systemConfig || !eventDetails || !formData.participants?.length) {
7201
+ if (!systemConfig || !eventDetails) {
7202
+ return;
7203
+ }
7204
+ if (freeformSelection) {
7205
+ if (!freeformSelection.customerEmail?.trim() ||
7206
+ !freeformSelection.customerName?.trim() ||
7207
+ !freeformSelection.start ||
7208
+ !freeformSelection.end) {
7209
+ return;
7210
+ }
7211
+ }
7212
+ else if (!formData.participants?.length) {
7122
7213
  return;
7123
7214
  }
7124
7215
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
7125
- if (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim()) {
7216
+ if (!freeformSelection &&
7217
+ (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
7126
7218
  return;
7127
7219
  }
7128
7220
  setIsCreatingPaymentIntent(true);
7129
7221
  setPaymentError(null);
7130
7222
  try {
7131
- const requestData = {
7132
- eventInstanceId: config.eventInstanceId || eventDetails.id,
7133
- organizationId: config.organizationId,
7134
- amount: Math.round(totalAmount),
7135
- currency: "eur",
7136
- participants: formData.participants.filter((p) => p.name?.trim()),
7137
- discountCode: discountCode?.code,
7138
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7139
- customerName: formData.customerName?.trim(),
7140
- customerEmail: formData.customerEmail?.trim(),
7141
- customerPhone: formData.customerPhone?.trim(),
7142
- comment: formData.comment?.trim(),
7143
- ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7144
- ...(paymentIntentId && { paymentIntentId }),
7145
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7146
- };
7147
- if (!requestData.eventInstanceId) {
7148
- throw new Error("Event instance ID is required");
7149
- }
7223
+ const endpoint = freeformSelection
7224
+ ? "/booking/create-freeform-stripe-payment"
7225
+ : "/booking/create-stripe-payment";
7226
+ const requestData = freeformSelection
7227
+ ? {
7228
+ eventTypeId: freeformSelection.eventTypeId,
7229
+ organizationId: config.organizationId,
7230
+ start: freeformSelection.start,
7231
+ end: freeformSelection.end,
7232
+ requestedUnits: freeformSelection.requestedUnits,
7233
+ amount: Math.round(totalAmount),
7234
+ currency: "eur",
7235
+ customerName: freeformSelection.customerName.trim(),
7236
+ customerEmail: freeformSelection.customerEmail.trim(),
7237
+ customerPhone: freeformSelection.customerPhone?.trim(),
7238
+ comment: freeformSelection.comment?.trim(),
7239
+ locale,
7240
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7241
+ ...(paymentIntentId && { paymentIntentId }),
7242
+ }
7243
+ : {
7244
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
7245
+ organizationId: config.organizationId,
7246
+ amount: Math.round(totalAmount),
7247
+ currency: "eur",
7248
+ participants: formData.participants.filter((p) => p.name?.trim()),
7249
+ discountCode: discountCode?.code,
7250
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7251
+ customerName: formData.customerName?.trim(),
7252
+ customerEmail: formData.customerEmail?.trim(),
7253
+ customerPhone: formData.customerPhone?.trim(),
7254
+ comment: formData.comment?.trim(),
7255
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7256
+ ...(paymentIntentId && { paymentIntentId }),
7257
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7258
+ };
7150
7259
  if (!requestData.organizationId) {
7151
7260
  throw new Error("Organization ID is required");
7152
7261
  }
7153
- if (!requestData.amount || requestData.amount <= 0) {
7262
+ if (requestData.amount === undefined || requestData.amount < 0) {
7154
7263
  throw new Error("Valid amount is required");
7155
7264
  }
7156
- if (!requestData.participants || requestData.participants.length === 0) {
7265
+ if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
7266
+ throw new Error("Event instance ID is required");
7267
+ }
7268
+ if (!freeformSelection &&
7269
+ "participants" in requestData &&
7270
+ (!requestData.participants || requestData.participants.length === 0)) {
7157
7271
  throw new Error("At least one participant is required");
7158
7272
  }
7159
7273
  if (!requestData.customerEmail) {
7160
7274
  throw new Error("Customer email is required");
7161
7275
  }
7162
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
7276
+ const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
7163
7277
  method: "POST",
7164
7278
  headers: createApiHeaders(config, locale),
7165
7279
  body: JSON.stringify(createRequestBody(config, requestData)),
@@ -7204,9 +7318,13 @@
7204
7318
  giftCards,
7205
7319
  config,
7206
7320
  upsellSelections,
7321
+ freeformSelection,
7322
+ locale,
7207
7323
  ]);
7208
7324
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
7209
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7325
+ const isFullyCoveredByGiftCards = freeformSelection
7326
+ ? false
7327
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7210
7328
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
7211
7329
  return (u$2(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
7212
7330
  }
@@ -7245,7 +7363,7 @@
7245
7363
  setPaymentIntentId(null);
7246
7364
  setClientSecret(null);
7247
7365
  onSuccess(result);
7248
- }, onError: onError }) }));
7366
+ }, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
7249
7367
  }
7250
7368
 
7251
7369
  const HOLD_DURATION_SECONDS = 15 * 60;
@@ -13505,20 +13623,22 @@
13505
13623
  const labelStyles = formStyles.label;
13506
13624
  const inputStyles = formStyles.input;
13507
13625
  const errorTextStyles = formStyles.error;
13508
- function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13626
+ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13509
13627
  const t = useTranslations();
13510
13628
  const { locale } = useLocale();
13511
13629
  const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
13512
13630
  // Form state
13513
- // Show voucher type selection if both monetary and event vouchers are allowed
13514
- // The event type selection will handle the case when no event types are available
13515
13631
  const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
13516
13632
  const canUseEventVoucherType = voucherConfig.allowEventVouchers;
13517
- const showVoucherTypeSelection = canUseMonetaryVoucherType && canUseEventVoucherType && !isLoadingEventTypes;
13633
+ const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
13634
+ const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
13635
+ const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
13518
13636
  const [voucherType, setVoucherType] = d$1(canUseMonetaryVoucherType ? "monetary" : "event");
13519
13637
  const [monetaryAmount, setMonetaryAmount] = d$1(voucherConfig.monetaryPresets[2] || 10000);
13520
13638
  const [selectedEventTypeId, setSelectedEventTypeId] = d$1(null);
13521
13639
  const [eventQuantity, setEventQuantity] = d$1(1);
13640
+ const [selectedCategoryId, setSelectedCategoryId] = d$1(null);
13641
+ const [categoryAmount, setCategoryAmount] = d$1(10000);
13522
13642
  const [recipientName, setRecipientName] = d$1("");
13523
13643
  const [message, setMessage] = d$1("");
13524
13644
  const [customerName, setCustomerName] = d$1("");
@@ -13578,6 +13698,9 @@
13578
13698
  if (voucherType === "monetary") {
13579
13699
  return monetaryAmount;
13580
13700
  }
13701
+ else if (voucherType === "category") {
13702
+ return categoryAmount;
13703
+ }
13581
13704
  else {
13582
13705
  const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
13583
13706
  if (eventType) {
@@ -13585,7 +13708,7 @@
13585
13708
  }
13586
13709
  return 0;
13587
13710
  }
13588
- }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
13711
+ }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
13589
13712
  const selectedEventType = T$2(() => {
13590
13713
  return eventTypes.find((et) => et.id === selectedEventTypeId);
13591
13714
  }, [eventTypes, selectedEventTypeId]);
@@ -13594,6 +13717,8 @@
13594
13717
  voucherType,
13595
13718
  selectedEventTypeId,
13596
13719
  eventQuantity,
13720
+ selectedCategoryId,
13721
+ categoryAmount,
13597
13722
  recipientName: recipientName.trim(),
13598
13723
  message: message.trim(),
13599
13724
  customerName: customerName.trim(),
@@ -13603,6 +13728,8 @@
13603
13728
  voucherType,
13604
13729
  selectedEventTypeId,
13605
13730
  eventQuantity,
13731
+ selectedCategoryId,
13732
+ categoryAmount,
13606
13733
  recipientName,
13607
13734
  message,
13608
13735
  customerName,
@@ -13621,24 +13748,37 @@
13621
13748
  return false;
13622
13749
  if (voucherType === "event" && !selectedEventTypeId)
13623
13750
  return false;
13751
+ if (voucherType === "category" && !selectedCategoryId)
13752
+ return false;
13624
13753
  return true;
13625
- }, [customerName, customerEmail, acceptTerms, totalAmount, voucherConfig, voucherType, selectedEventTypeId]);
13754
+ }, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
13626
13755
  const defaultMonetaryAmount = T$2(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
13627
13756
  const handleVoucherTypeChange = q$2((nextType) => {
13628
13757
  setVoucherType(nextType);
13629
13758
  setPaymentError(null);
13630
13759
  if (nextType === "event") {
13631
- // Keep voucher type mutually exclusive: entering event flow resets amount selection state.
13632
13760
  setMonetaryAmount(defaultMonetaryAmount);
13761
+ setSelectedCategoryId(null);
13762
+ setCategoryAmount(10000);
13633
13763
  if (eventTypes.length === 1) {
13634
13764
  setSelectedEventTypeId(eventTypes[0]?.id ?? null);
13635
13765
  }
13636
13766
  return;
13637
13767
  }
13638
- // Keep voucher type mutually exclusive: entering amount flow clears event selection.
13768
+ if (nextType === "category") {
13769
+ setMonetaryAmount(defaultMonetaryAmount);
13770
+ setSelectedEventTypeId(null);
13771
+ setEventQuantity(1);
13772
+ if (categories.length === 1) {
13773
+ setSelectedCategoryId(categories[0]?.id ?? null);
13774
+ }
13775
+ return;
13776
+ }
13639
13777
  setSelectedEventTypeId(null);
13640
13778
  setEventQuantity(1);
13641
- }, [defaultMonetaryAmount, eventTypes]);
13779
+ setSelectedCategoryId(null);
13780
+ setCategoryAmount(10000);
13781
+ }, [defaultMonetaryAmount, eventTypes, categories]);
13642
13782
  y$1(() => {
13643
13783
  if (!canUseMonetaryVoucherType && voucherType === "monetary") {
13644
13784
  setVoucherType("event");
@@ -13679,9 +13819,12 @@
13679
13819
  organizationId: config.organizationId,
13680
13820
  amount: totalAmount,
13681
13821
  currency: "eur",
13682
- voucherType,
13683
- monetaryValue: voucherType === "monetary" ? totalAmount : undefined,
13822
+ voucherType: voucherType === "category" ? "monetary" : voucherType,
13823
+ monetaryValue: voucherType === "monetary" ? totalAmount
13824
+ : voucherType === "category" ? categoryAmount
13825
+ : undefined,
13684
13826
  eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
13827
+ eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
13685
13828
  eventAmount: voucherType === "event" ? eventQuantity : undefined,
13686
13829
  recipientName: recipientName.trim() || undefined,
13687
13830
  message: message.trim() || undefined,
@@ -13872,7 +14015,30 @@
13872
14015
  fontSize: "13px",
13873
14016
  color: "var(--bw-text-muted)",
13874
14017
  marginTop: "4px",
13875
- }, children: t("voucher.eventTypeDesc") })] }))] })] })), voucherType === "monetary" && (u$2("div", { style: cardStyles, children: [u$2("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), u$2("div", { style: {
14018
+ }, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (u$2("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
14019
+ flex: 1,
14020
+ padding: "16px",
14021
+ borderRadius: "var(--bw-border-radius)",
14022
+ border: voucherType === "category"
14023
+ ? "2px solid var(--bw-highlight-color)"
14024
+ : "1px solid var(--bw-border-color)",
14025
+ backgroundColor: voucherType === "category"
14026
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
14027
+ : "var(--bw-surface-color)",
14028
+ cursor: "pointer",
14029
+ fontFamily: "var(--bw-font-family)",
14030
+ transition: "all 0.2s ease",
14031
+ textAlign: "center",
14032
+ }, children: [u$2("div", { style: {
14033
+ fontWeight: 600,
14034
+ color: voucherType === "category"
14035
+ ? "var(--bw-highlight-color)"
14036
+ : "var(--bw-text-color)",
14037
+ }, children: t("voucher.categoryType") }), u$2("div", { style: {
14038
+ fontSize: "13px",
14039
+ color: "var(--bw-text-muted)",
14040
+ marginTop: "4px",
14041
+ }, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (u$2("div", { style: cardStyles, children: [u$2("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), u$2("div", { style: {
13876
14042
  display: "grid",
13877
14043
  gridTemplateColumns: "repeat(3, 1fr)",
13878
14044
  gap: "8px",
@@ -13927,7 +14093,50 @@
13927
14093
  fontSize: "18px",
13928
14094
  color: "var(--bw-highlight-color)",
13929
14095
  fontFamily: "var(--bw-font-family)",
13930
- }, children: formatCurrency(totalAmount) })] }) }))] })), u$2("div", { style: cardStyles, children: [u$2("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), u$2("p", { style: {
14096
+ }, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (u$2("div", { style: cardStyles, children: [u$2("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), u$2("div", { style: { marginTop: "12px" }, children: u$2("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [u$2("div", { children: [u$2("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), u$2("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
14097
+ ...inputStyles,
14098
+ cursor: categories.length === 1 ? "not-allowed" : "pointer",
14099
+ }, children: [categories.length > 1 && (u$2("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (u$2("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (u$2("div", { children: [u$2("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), u$2("div", { style: {
14100
+ display: "grid",
14101
+ gridTemplateColumns: "repeat(3, 1fr)",
14102
+ gap: "8px",
14103
+ marginTop: "4px",
14104
+ }, children: voucherConfig.monetaryPresets.map((preset) => (u$2("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
14105
+ padding: "10px",
14106
+ borderRadius: "var(--bw-border-radius)",
14107
+ border: categoryAmount === preset
14108
+ ? "2px solid var(--bw-highlight-color)"
14109
+ : "1px solid var(--bw-border-color)",
14110
+ backgroundColor: categoryAmount === preset
14111
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
14112
+ : "var(--bw-surface-color)",
14113
+ cursor: "pointer",
14114
+ fontFamily: "var(--bw-font-family)",
14115
+ fontWeight: 600,
14116
+ fontSize: "14px",
14117
+ color: categoryAmount === preset
14118
+ ? "var(--bw-highlight-color)"
14119
+ : "var(--bw-text-color)",
14120
+ transition: "all 0.2s ease",
14121
+ }, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (u$2("div", { style: {
14122
+ marginTop: "16px",
14123
+ padding: "12px",
14124
+ backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
14125
+ borderRadius: "var(--bw-border-radius)",
14126
+ border: "1px solid var(--bw-highlight-color)",
14127
+ }, children: u$2("div", { style: {
14128
+ display: "flex",
14129
+ justifyContent: "space-between",
14130
+ alignItems: "center",
14131
+ }, children: [u$2("span", { style: {
14132
+ color: "var(--bw-text-color)",
14133
+ fontFamily: "var(--bw-font-family)",
14134
+ }, children: categories.find((c) => c.id === selectedCategoryId)?.name }), u$2("span", { style: {
14135
+ fontWeight: 700,
14136
+ fontSize: "18px",
14137
+ color: "var(--bw-highlight-color)",
14138
+ fontFamily: "var(--bw-font-family)",
14139
+ }, children: formatCurrency(categoryAmount) })] }) }))] })), u$2("div", { style: cardStyles, children: [u$2("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), u$2("p", { style: {
13931
14140
  fontSize: "13px",
13932
14141
  color: "var(--bw-text-muted)",
13933
14142
  fontFamily: "var(--bw-font-family)",
@@ -13960,7 +14169,9 @@
13960
14169
  fontFamily: "var(--bw-font-family)",
13961
14170
  }, children: voucherType === "monetary"
13962
14171
  ? t("voucher.monetaryVoucher")
13963
- : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (u$2("div", { style: {
14172
+ : voucherType === "category"
14173
+ ? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
14174
+ : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (u$2("div", { style: {
13964
14175
  fontSize: "13px",
13965
14176
  color: "var(--bw-text-muted)",
13966
14177
  fontFamily: "var(--bw-font-family)",
@@ -14236,7 +14447,7 @@
14236
14447
  }, children: t("voucher.buyAsGift") })] }));
14237
14448
  }
14238
14449
 
14239
- function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14450
+ function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14240
14451
  if (!voucherConfig?.enabled) {
14241
14452
  return null;
14242
14453
  }
@@ -14247,7 +14458,7 @@
14247
14458
  justifyContent: "center",
14248
14459
  }, children: u$2(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
14249
14460
  ? Math.min(...eventTypes.map((et) => et.maxPrice))
14250
- : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), u$2(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (u$2(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14461
+ : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), u$2(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, categories: categories, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (u$2(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14251
14462
  }
14252
14463
 
14253
14464
  // Helper function to preprocess markdown for underline support
@@ -15515,6 +15726,312 @@
15515
15726
  }) })] }));
15516
15727
  }
15517
15728
 
15729
+ function toMinutesForUnit(unit) {
15730
+ switch (unit.toLowerCase()) {
15731
+ case "minute":
15732
+ case "minutes":
15733
+ return 1;
15734
+ case "hour":
15735
+ case "hours":
15736
+ return 60;
15737
+ case "day":
15738
+ case "days":
15739
+ return 24 * 60;
15740
+ default:
15741
+ return 1;
15742
+ }
15743
+ }
15744
+ // --- Timezone helpers ------------------------------------------------------
15745
+ // The booking is evaluated server-side against the organization's local
15746
+ // operating hours. The customer's browser may be in a different timezone, so we
15747
+ // interpret the datetime-local field as a wall-clock time in the ORG timezone
15748
+ // (not the browser's) and convert to a UTC instant for the API.
15749
+ function getTzParts(date, timeZone) {
15750
+ const dtf = new Intl.DateTimeFormat("en-CA", {
15751
+ timeZone,
15752
+ hour12: false,
15753
+ year: "numeric",
15754
+ month: "2-digit",
15755
+ day: "2-digit",
15756
+ hour: "2-digit",
15757
+ minute: "2-digit",
15758
+ second: "2-digit",
15759
+ });
15760
+ const parts = {};
15761
+ for (const part of dtf.formatToParts(date)) {
15762
+ if (part.type !== "literal")
15763
+ parts[part.type] = part.value;
15764
+ }
15765
+ return parts;
15766
+ }
15767
+ function tzOffsetMs(utcMs, timeZone) {
15768
+ const p = getTzParts(new Date(utcMs), timeZone);
15769
+ const hour = p.hour === "24" ? "00" : p.hour;
15770
+ const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
15771
+ return asLocalUtc - utcMs;
15772
+ }
15773
+ /** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
15774
+ function zonedWallTimeToUtc(wallTime, timeZone) {
15775
+ const [datePart, timePart] = wallTime.split("T");
15776
+ if (!datePart || !timePart)
15777
+ return new Date(NaN);
15778
+ const [y, mo, d] = datePart.split("-").map(Number);
15779
+ const [h, mi] = timePart.split(":").map(Number);
15780
+ const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
15781
+ // Single-pass offset correction (good enough outside the ~1h DST fold).
15782
+ const offset = tzOffsetMs(naiveUtc, timeZone);
15783
+ return new Date(naiveUtc - offset);
15784
+ }
15785
+ /** Render a UTC instant as an org-tz datetime-local input value. */
15786
+ function utcToZonedInputValue(date, timeZone) {
15787
+ const p = getTzParts(date, timeZone);
15788
+ const hour = p.hour === "24" ? "00" : p.hour;
15789
+ return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
15790
+ }
15791
+ /** Format a UTC instant for display in the org timezone. */
15792
+ function formatInTimeZone(date, timeZone, locale) {
15793
+ return new Intl.DateTimeFormat(locale, {
15794
+ timeZone,
15795
+ dateStyle: "medium",
15796
+ timeStyle: "short",
15797
+ }).format(date);
15798
+ }
15799
+ function calculateTieredPrice(durationUnits, config) {
15800
+ const tiers = [...(config.tiers ?? [])]
15801
+ .filter((tier) => tier.unitCount > 0 && tier.price >= 0)
15802
+ .sort((a, b) => b.unitCount - a.unitCount);
15803
+ if (tiers.length === 0) {
15804
+ return 0;
15805
+ }
15806
+ let total = 0;
15807
+ let remaining = durationUnits;
15808
+ for (const tier of tiers) {
15809
+ const count = Math.floor(remaining / tier.unitCount);
15810
+ if (count <= 0)
15811
+ continue;
15812
+ total += count * tier.price;
15813
+ remaining -= count * tier.unitCount;
15814
+ }
15815
+ if (remaining > 0) {
15816
+ if (typeof config.perUnitPrice === "number") {
15817
+ total += remaining * config.perUnitPrice;
15818
+ }
15819
+ else {
15820
+ total += tiers[tiers.length - 1]?.price ?? 0;
15821
+ }
15822
+ }
15823
+ return total;
15824
+ }
15825
+ function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
15826
+ const t = useTranslations();
15827
+ const { locale } = useLocale();
15828
+ const freeformConfig = eventType.freeformConfig;
15829
+ const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
15830
+ const isNamedResource = eventType.resourceKind === "named";
15831
+ const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
15832
+ const now = T$2(() => {
15833
+ const next = new Date();
15834
+ next.setMinutes(next.getMinutes() + 30, 0, 0);
15835
+ return next;
15836
+ }, []);
15837
+ const [startInput, setStartInput] = d$1(utcToZonedInputValue(now, timeZone));
15838
+ const [duration, setDuration] = d$1(Math.max(1, freeformConfig?.minDuration ?? 1));
15839
+ const [requestedUnits, setRequestedUnits] = d$1(1);
15840
+ const [customerName, setCustomerName] = d$1("");
15841
+ const [customerEmail, setCustomerEmail] = d$1("");
15842
+ const [customerPhone, setCustomerPhone] = d$1("");
15843
+ const [comment, setComment] = d$1("");
15844
+ const [acceptTerms, setAcceptTerms] = d$1(false);
15845
+ const [checkoutStep, setCheckoutStep] = d$1("details");
15846
+ const [availability, setAvailability] = d$1(null);
15847
+ const [availabilityError, setAvailabilityError] = d$1(null);
15848
+ const [isCheckingAvailability, setIsCheckingAvailability] = d$1(false);
15849
+ y$1(() => {
15850
+ if (!isOpen)
15851
+ return;
15852
+ setCheckoutStep("details");
15853
+ }, [isOpen]);
15854
+ y$1(() => {
15855
+ if (!freeformConfig)
15856
+ return;
15857
+ setDuration(Math.max(1, freeformConfig.minDuration));
15858
+ setRequestedUnits(1);
15859
+ setAvailability(null);
15860
+ setAvailabilityError(null);
15861
+ }, [freeformConfig?.resourceId]);
15862
+ const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
15863
+ const startDate = T$2(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
15864
+ const endDate = T$2(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
15865
+ y$1(() => {
15866
+ if (!isOpen || !freeformConfig)
15867
+ return;
15868
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
15869
+ return;
15870
+ const timer = setTimeout(async () => {
15871
+ setIsCheckingAvailability(true);
15872
+ setAvailabilityError(null);
15873
+ try {
15874
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
15875
+ method: "POST",
15876
+ headers: createApiHeaders(config, locale),
15877
+ body: JSON.stringify(createRequestBody(config, {
15878
+ eventTypeId: eventType.id,
15879
+ requestedUnits,
15880
+ start: startDate.toISOString(),
15881
+ end: endDate.toISOString(),
15882
+ })),
15883
+ });
15884
+ const data = await response.json();
15885
+ if (!response.ok) {
15886
+ setAvailability(null);
15887
+ setAvailabilityError(data.error || t("error.loadBookingData"));
15888
+ return;
15889
+ }
15890
+ setAvailability({
15891
+ available: Boolean(data.available),
15892
+ freeUnits: Number(data.freeUnits ?? 0),
15893
+ capacityUnits: Number(data.capacityUnits ?? 0),
15894
+ usedUnits: Number(data.usedUnits ?? 0),
15895
+ reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
15896
+ blockUsedUnits: Number(data.blockUsedUnits ?? 0),
15897
+ durationUnits: Number(data.durationUnits ?? 0),
15898
+ roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
15899
+ });
15900
+ }
15901
+ catch (_error) {
15902
+ setAvailability(null);
15903
+ setAvailabilityError(t("error.loadBookingData"));
15904
+ }
15905
+ finally {
15906
+ setIsCheckingAvailability(false);
15907
+ }
15908
+ }, 350);
15909
+ return () => clearTimeout(timer);
15910
+ }, [
15911
+ config,
15912
+ endDate,
15913
+ eventType.id,
15914
+ freeformConfig,
15915
+ isOpen,
15916
+ locale,
15917
+ requestedUnits,
15918
+ startDate,
15919
+ t,
15920
+ ]);
15921
+ const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
15922
+ const basePrice = T$2(() => {
15923
+ if (!freeformConfig)
15924
+ return 0;
15925
+ if (freeformConfig.pricingModel === "perUnit") {
15926
+ return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
15927
+ }
15928
+ return calculateTieredPrice(billedDurationUnits, freeformConfig);
15929
+ }, [billedDurationUnits, freeformConfig]);
15930
+ const totalPrice = Math.max(0, basePrice * requestedUnits);
15931
+ const canContinueToPayment = !!freeformConfig &&
15932
+ customerName.trim().length >= 2 &&
15933
+ customerEmail.trim().length > 3 &&
15934
+ acceptTerms &&
15935
+ !!availability?.available &&
15936
+ !isCheckingAvailability;
15937
+ const freeformSelection = T$2(() => ({
15938
+ eventTypeId: eventType.id,
15939
+ start: startDate.toISOString(),
15940
+ end: endDate.toISOString(),
15941
+ requestedUnits,
15942
+ customerName: customerName.trim(),
15943
+ customerEmail: customerEmail.trim(),
15944
+ customerPhone: customerPhone.trim(),
15945
+ comment: comment.trim(),
15946
+ }), [
15947
+ comment,
15948
+ customerEmail,
15949
+ customerName,
15950
+ customerPhone,
15951
+ endDate,
15952
+ eventType.id,
15953
+ requestedUnits,
15954
+ startDate,
15955
+ ]);
15956
+ const paymentFormData = T$2(() => ({
15957
+ customerName: customerName.trim(),
15958
+ customerEmail: customerEmail.trim(),
15959
+ customerPhone: customerPhone.trim(),
15960
+ participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
15961
+ comment: comment.trim(),
15962
+ }), [comment, customerEmail, customerName, customerPhone]);
15963
+ const eventDetailsForPayment = T$2(() => ({
15964
+ id: eventType.id,
15965
+ name: eventType.name,
15966
+ startTime: startDate.toISOString(),
15967
+ endTime: endDate.toISOString(),
15968
+ price: basePrice,
15969
+ maxParticipants: availability?.capacityUnits ?? requestedUnits,
15970
+ participantCount: 0,
15971
+ availableSpots: availability?.freeUnits ?? requestedUnits,
15972
+ durationDays: 1,
15973
+ durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
15974
+ images: eventType.images ?? [],
15975
+ category: eventType.category,
15976
+ organization: {
15977
+ id: config.organizationId,
15978
+ name: "",
15979
+ },
15980
+ bookingOpen: availability?.available ?? false,
15981
+ }), [
15982
+ availability?.available,
15983
+ availability?.capacityUnits,
15984
+ availability?.freeUnits,
15985
+ availability?.roundedDurationMinutes,
15986
+ basePrice,
15987
+ config.organizationId,
15988
+ duration,
15989
+ endDate,
15990
+ eventType.category,
15991
+ eventType.id,
15992
+ eventType.images,
15993
+ eventType.name,
15994
+ requestedUnits,
15995
+ startDate,
15996
+ unitMinutes,
15997
+ ]);
15998
+ const footer = (u$2("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [u$2("button", { type: "button", onClick: () => {
15999
+ if (checkoutStep === "payment") {
16000
+ setCheckoutStep("details");
16001
+ }
16002
+ else {
16003
+ onClose();
16004
+ }
16005
+ }, style: {
16006
+ flex: 1,
16007
+ padding: "12px 16px",
16008
+ border: "1px solid var(--bw-border-color)",
16009
+ borderRadius: "var(--bw-border-radius)",
16010
+ backgroundColor: "var(--bw-surface-color)",
16011
+ color: "var(--bw-text-color)",
16012
+ fontFamily: "var(--bw-font-family)",
16013
+ fontWeight: 600,
16014
+ cursor: "pointer",
16015
+ }, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (u$2("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
16016
+ flex: 1,
16017
+ padding: "12px 16px",
16018
+ border: "none",
16019
+ borderRadius: "var(--bw-border-radius)",
16020
+ backgroundColor: "var(--bw-highlight-color)",
16021
+ color: "var(--bw-button-text-color, #ffffff)",
16022
+ fontFamily: "var(--bw-font-family)",
16023
+ fontWeight: 700,
16024
+ cursor: canContinueToPayment ? "pointer" : "not-allowed",
16025
+ opacity: canContinueToPayment ? 1 : 0.6,
16026
+ }, children: t("button.continueToPayment") }))] }));
16027
+ if (!freeformConfig) {
16028
+ return (u$2(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: u$2("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
16029
+ }
16030
+ return (u$2(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: u$2("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (u$2(k$3, { children: [u$2("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [u$2("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), u$2("div", { style: { display: "grid", gap: "10px" }, children: [u$2("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), u$2("input", { type: "datetime-local", value: startInput, onChange: (event) => setStartInput(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), u$2("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), u$2("input", { type: "number", min: freeformConfig.minDuration, max: freeformConfig.maxDuration, step: freeformConfig.stepDuration, value: duration, onChange: (event) => setDuration(Number(event.target.value)), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }), !isNamedResource && (u$2("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), u$2("input", { type: "number", min: 1, max: maxUnits, value: requestedUnits, onChange: (event) => setRequestedUnits(Math.min(maxUnits, Math.max(1, Number(event.target.value)))), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } })] }))] }), u$2("div", { style: { marginTop: "12px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: [formatInTimeZone(startDate, timeZone, locale), " - ", formatInTimeZone(endDate, timeZone, locale), eventType.organizationTimezone ? ` (${timeZone})` : ""] }), isCheckingAvailability && (u$2("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (u$2("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (u$2("div", { style: { marginTop: "8px", fontSize: "13px", color: availability.available ? "var(--bw-success-color)" : "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availability.available
16031
+ ? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
16032
+ : t("booking.freeformNotAvailable") }))] }), u$2("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [u$2("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), u$2("div", { style: { display: "grid", gap: "10px" }, children: [u$2("input", { type: "text", placeholder: t("booking.namePlaceholder"), value: customerName, onChange: (event) => setCustomerName(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), u$2("input", { type: "email", placeholder: t("booking.emailPlaceholder"), value: customerEmail, onChange: (event) => setCustomerEmail(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), u$2("input", { type: "tel", placeholder: t("booking.phonePlaceholder"), value: customerPhone, onChange: (event) => setCustomerPhone(event.target.value), style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)" } }), u$2("textarea", { placeholder: t("booking.commentPlaceholder"), value: comment, onChange: (event) => setComment(event.target.value), rows: 3, style: { padding: "10px", borderRadius: "var(--bw-border-radius-small)", border: "1px solid var(--bw-border-color)", backgroundColor: "var(--bw-background-color)", color: "var(--bw-text-color)", resize: "vertical" } }), u$2("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [u$2("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), u$2("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [u$2("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), u$2("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [u$2("span", { children: t("booking.duration") }), u$2("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), u$2("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [u$2("span", { children: t("booking.price") }), u$2("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), u$2("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-color)", fontFamily: "var(--bw-font-family)", fontSize: "20px", fontWeight: 700, marginTop: "10px" }, children: [u$2("span", { children: t("summary.totalAmount") }), u$2("span", { children: formatCurrency(totalPrice) })] })] })] })) : (u$2(k$3, { children: [u$2(HoldCountdown, {}), u$2("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [u$2("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (u$2("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (u$2(MolliePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode, freeformSelection: freeformSelection })), systemConfig?.paymentProvider === "stripe" && (u$2(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
16033
+ }
16034
+
15518
16035
  const getThemeConfig = (theme = "generic") => {
15519
16036
  switch (theme) {
15520
16037
  case "christmas":
@@ -16155,6 +16672,7 @@
16155
16672
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = d$1(false);
16156
16673
  const [shouldRenderUpsells, setShouldRenderUpsells] = d$1(false);
16157
16674
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = d$1(false);
16675
+ const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = d$1(false);
16158
16676
  // Google Ads config (received from API, set once from the first API response)
16159
16677
  const [googleAdsConfig, setGoogleAdsConfig] = d$1(null);
16160
16678
  const extractGoogleAdsConfig = (data) => {
@@ -16168,6 +16686,7 @@
16168
16686
  // Voucher purchase state
16169
16687
  const [voucherConfig, setVoucherConfig] = d$1(null);
16170
16688
  const [voucherEventTypes, setVoucherEventTypes] = d$1([]);
16689
+ const [voucherCategories, setVoucherCategories] = d$1([]);
16171
16690
  const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = d$1(false);
16172
16691
  const [isVoucherFormOpen, setIsVoucherFormOpen] = d$1(false);
16173
16692
  const [voucherPurchaseResult, setVoucherPurchaseResult] = d$1(null);
@@ -16247,6 +16766,7 @@
16247
16766
  setVoucherConfig(mergedConfig);
16248
16767
  extractGoogleAdsConfig(data);
16249
16768
  setVoucherEventTypes(data.eventTypes || []);
16769
+ setVoucherCategories(data.categories || []);
16250
16770
  // Set system config for payment processing
16251
16771
  if (data.paymentProvider) {
16252
16772
  setSystemConfig({
@@ -16275,7 +16795,7 @@
16275
16795
  if (!analyticsInitRef.current && config.organizationId) {
16276
16796
  analyticsInitRef.current = true;
16277
16797
  initAnalytics(config.apiBaseUrl, config.organizationId, {
16278
- partnerContractId: config.partnerContractId,
16798
+ ...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
16279
16799
  });
16280
16800
  trackEvent("widget_loaded", {
16281
16801
  viewMode,
@@ -16754,6 +17274,18 @@
16754
17274
  const handleEventTypeSelect = async (eventType) => {
16755
17275
  trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
16756
17276
  setSelectedEventType(eventType);
17277
+ if (eventType.bookingMode === "freeform") {
17278
+ setCurrentStep("freeform");
17279
+ setShouldRenderFreeformSelection(true);
17280
+ setIsLoadingEventInstances(true);
17281
+ try {
17282
+ await loadEventInstances(eventType.id);
17283
+ }
17284
+ finally {
17285
+ setIsLoadingEventInstances(false);
17286
+ }
17287
+ return;
17288
+ }
16757
17289
  setCurrentStep("eventInstances");
16758
17290
  setShouldRenderInstanceSelection(true);
16759
17291
  setIsLoadingEventInstances(true);
@@ -16844,6 +17376,7 @@
16844
17376
  setSuccessPaymentId(result.paymentIntent.id);
16845
17377
  setSidebarOpen(false);
16846
17378
  setShouldRenderBookingForm(false);
17379
+ setShouldRenderFreeformSelection(false);
16847
17380
  setBookingPersistedState(null);
16848
17381
  config.onSuccess?.(result);
16849
17382
  };
@@ -17159,6 +17692,7 @@
17159
17692
  setShouldRenderInstanceSelection(false);
17160
17693
  setShouldRenderUpsells(false);
17161
17694
  setShouldRenderBookingForm(false);
17695
+ setShouldRenderFreeformSelection(false);
17162
17696
  setSelectedUpsells([]);
17163
17697
  setUpsells([]);
17164
17698
  setBookingPersistedState(null);
@@ -17180,6 +17714,7 @@
17180
17714
  setShouldRenderInstanceSelection(false);
17181
17715
  setShouldRenderUpsells(false);
17182
17716
  setShouldRenderBookingForm(false);
17717
+ setShouldRenderFreeformSelection(false);
17183
17718
  setSelectedUpsells([]);
17184
17719
  setUpsells([]);
17185
17720
  setBookingPersistedState(null);
@@ -17199,6 +17734,7 @@
17199
17734
  setSuccessPaymentId(null);
17200
17735
  setShouldRenderInstanceSelection(false);
17201
17736
  setShouldRenderBookingForm(false);
17737
+ setShouldRenderFreeformSelection(false);
17202
17738
  const url = new URL(window.location.href);
17203
17739
  url.searchParams.delete("payment_intent");
17204
17740
  url.searchParams.delete("payment_intent_client_secret");
@@ -17242,13 +17778,23 @@
17242
17778
  setCurrentStep("booking");
17243
17779
  setShouldRenderBookingForm(true);
17244
17780
  }
17781
+ else if (selectedEventType?.bookingMode === "freeform") {
17782
+ setCurrentStep("freeform");
17783
+ setShouldRenderFreeformSelection(true);
17784
+ }
17245
17785
  else {
17246
17786
  setCurrentStep("eventInstances");
17247
17787
  setSidebarOpen(true);
17248
17788
  setShouldRenderInstanceSelection(true);
17249
17789
  }
17250
17790
  }, children: [isButtonBusy && u$2(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
17251
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (u$2(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (u$2(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (u$2(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), u$2(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17791
+ (isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
17792
+ ? t("button.bookNow")
17793
+ : t("button.viewDates"))] }), shouldRenderInstanceSelection && (u$2(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (u$2(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (u$2(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (u$2(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17794
+ setCurrentStep("eventTypes");
17795
+ setSidebarOpen(false);
17796
+ setShouldRenderFreeformSelection(false);
17797
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), u$2(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17252
17798
  setIsSuccess(false);
17253
17799
  setCurrentStep("eventTypes");
17254
17800
  setSidebarOpen(false);
@@ -17256,6 +17802,7 @@
17256
17802
  setShouldRenderInstanceSelection(false);
17257
17803
  setShouldRenderUpsells(false);
17258
17804
  setShouldRenderBookingForm(false);
17805
+ setShouldRenderFreeformSelection(false);
17259
17806
  setSelectedUpsells([]);
17260
17807
  setUpsells([]);
17261
17808
  const url = new URL(window.location.href);
@@ -17268,7 +17815,7 @@
17268
17815
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
17269
17816
  }
17270
17817
  // Cards mode (default) - show event type selection with optional voucher card
17271
- const cardsView = (u$2(k$3, { children: [hasEventSelection && (u$2(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (u$2(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (u$2("div", { style: { padding: "24px", textAlign: "center" }, children: u$2("div", { style: {
17818
+ const cardsView = (u$2(k$3, { children: [hasEventSelection && (u$2(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (u$2(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (u$2("div", { style: { padding: "24px", textAlign: "center" }, children: u$2("div", { style: {
17272
17819
  display: "inline-block",
17273
17820
  width: "32px",
17274
17821
  height: "32px",
@@ -17309,13 +17856,17 @@
17309
17856
  };
17310
17857
  };
17311
17858
  const backHandlers = getBackHandlers();
17312
- return (u$2(StyleProvider, { config: config, children: [u$2("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (u$2(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (u$2(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (u$2(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), u$2(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17859
+ return (u$2(StyleProvider, { config: config, children: [u$2("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (u$2(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (u$2(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (u$2(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), shouldRenderFreeformSelection && selectedEventType?.bookingMode === "freeform" && (u$2(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17860
+ setCurrentStep("eventTypes");
17861
+ setShouldRenderFreeformSelection(false);
17862
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), u$2(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17313
17863
  setIsSuccess(false);
17314
17864
  setCurrentStep("eventTypes");
17315
17865
  setSuccessPaymentId(null);
17316
17866
  setShouldRenderInstanceSelection(false);
17317
17867
  setShouldRenderUpsells(false);
17318
17868
  setShouldRenderBookingForm(false);
17869
+ setShouldRenderFreeformSelection(false);
17319
17870
  setSelectedUpsells([]);
17320
17871
  setUpsells([]);
17321
17872
  const url = new URL(window.location.href);
@@ -17325,7 +17876,7 @@
17325
17876
  url.searchParams.delete("mollie_payment_id");
17326
17877
  url.searchParams.delete("mollie_status");
17327
17878
  window.history.replaceState({}, "", url.toString());
17328
- }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), u$2(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
17879
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), u$2(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, categories: voucherCategories, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
17329
17880
  setIsSuccess(false);
17330
17881
  setVoucherPurchaseResult(null);
17331
17882
  const url = new URL(window.location.href);
@@ -17378,7 +17929,7 @@
17378
17929
  }
17379
17930
  }
17380
17931
 
17381
- var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.bw-btn{letter-spacing:var(--bw-letter-spacing,normal);text-transform:var(--bw-text-transform,none);transition:all .2s ease!important}.bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.bw-btn:disabled,.bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}button[class*=bw-btn],button[style*=transition]{transition:all .2s ease!important}button[data-variant=primary]:hover:not(:disabled),button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
17932
+ var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.booking-widget-container .bw-btn{letter-spacing:var(--bw-letter-spacing,normal);text-transform:var(--bw-text-transform,none);transition:all .2s ease!important}.booking-widget-container .bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.booking-widget-container .bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.booking-widget-container .bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.booking-widget-container .bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.booking-widget-container .bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.booking-widget-container .bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.booking-widget-container .bw-btn:disabled,.booking-widget-container .bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}.booking-widget-container button[class*=bw-btn],.booking-widget-container button[style*=transition]{transition:all .2s ease!important}.booking-widget-container button[data-variant=primary]:hover:not(:disabled),.booking-widget-container button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}.booking-widget-container button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}.booking-widget-container button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}.booking-widget-container button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.booking-widget-container .bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.booking-widget-container .bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
17382
17933
  styleInject(css_248z);
17383
17934
 
17384
17935
  // Export init function for vanilla JS usage with Preact