@bigz-app/booking-widget 1.4.5 → 1.5.2

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 (54) hide show
  1. package/dist/booking-widget.js +710 -112
  2. package/dist/booking-widget.js.map +1 -1
  3. package/dist/components/UniversalBookingWidget.d.ts +21 -105
  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/EventInstanceSelection.d.ts +3 -1
  10. package/dist/components/events/EventInstanceSelection.d.ts.map +1 -1
  11. package/dist/components/events/EventTypeSelection.d.ts +17 -1
  12. package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
  13. package/dist/components/events/FreeformSelection.d.ts +49 -0
  14. package/dist/components/events/FreeformSelection.d.ts.map +1 -0
  15. package/dist/components/events/NextEventsPreview.d.ts +1 -1
  16. package/dist/components/events/NextEventsPreview.d.ts.map +1 -1
  17. package/dist/components/events/SpecialsView.d.ts +1 -1
  18. package/dist/components/events/SpecialsView.d.ts.map +1 -1
  19. package/dist/components/events/index.d.ts +1 -0
  20. package/dist/components/events/index.d.ts.map +1 -1
  21. package/dist/components/events/types.d.ts +42 -0
  22. package/dist/components/events/types.d.ts.map +1 -0
  23. package/dist/components/upsells/UpsellCard.d.ts +1 -1
  24. package/dist/components/upsells/UpsellCard.d.ts.map +1 -1
  25. package/dist/components/upsells/UpsellsStep.d.ts +2 -44
  26. package/dist/components/upsells/UpsellsStep.d.ts.map +1 -1
  27. package/dist/components/upsells/types.d.ts +44 -0
  28. package/dist/components/upsells/types.d.ts.map +1 -0
  29. package/dist/components/voucher/VoucherIntegration.d.ts +3 -2
  30. package/dist/components/voucher/VoucherIntegration.d.ts.map +1 -1
  31. package/dist/components/voucher/VoucherPurchaseForm.d.ts +9 -16
  32. package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
  33. package/dist/components/voucher/index.d.ts +1 -1
  34. package/dist/components/voucher/index.d.ts.map +1 -1
  35. package/dist/components/voucher/types.d.ts +15 -0
  36. package/dist/components/voucher/types.d.ts.map +1 -0
  37. package/dist/components/voucher/useVoucherConfig.d.ts +6 -0
  38. package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
  39. package/dist/config.d.ts +66 -0
  40. package/dist/config.d.ts.map +1 -0
  41. package/dist/i18n/locales/de.d.ts.map +1 -1
  42. package/dist/i18n/locales/en.d.ts.map +1 -1
  43. package/dist/i18n/locales/es.d.ts.map +1 -1
  44. package/dist/i18n/locales/pt.d.ts.map +1 -1
  45. package/dist/i18n/locales/sv.d.ts.map +1 -1
  46. package/dist/index.cjs +710 -112
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.esm.js +710 -112
  49. package/dist/index.esm.js.map +1 -1
  50. package/dist/styles/StyleProvider.d.ts +1 -1
  51. package/dist/styles/StyleProvider.d.ts.map +1 -1
  52. package/dist/utils.d.ts +1 -1
  53. package/dist/utils.d.ts.map +1 -1
  54. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -169,6 +169,10 @@ const de$1 = {
169
169
  "booking.stepUpsells": "Extras",
170
170
  "booking.stepDetails": "Angaben",
171
171
  "booking.stepPayment": "Zahlung",
172
+ "booking.steps": "{{count}} Schritt(e)",
173
+ "booking.freeformUnitsAvailable": "{{count}} Einheiten verfuegbar",
174
+ "booking.freeformNotAvailable": "Das gewaehlte Zeitfenster ist nicht verfuegbar",
175
+ "booking.freeformMissingConfig": "Die Freeform-Konfiguration fuer diesen Event-Typ fehlt.",
172
176
  // Booking summary
173
177
  "summary.title": "Buchungszusammenfassung",
174
178
  "summary.subtotal": "Zwischensumme:",
@@ -224,6 +228,13 @@ const de$1 = {
224
228
  "voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
225
229
  "voucher.eventType": "Erlebnisgutschein",
226
230
  "voucher.eventTypeDesc": "Für ein bestimmtes Erlebnis",
231
+ "voucher.categoryType": "Kategoriegutschein",
232
+ "voucher.categoryTypeDesc": "Für ein beliebiges Erlebnis einer Kategorie",
233
+ "voucher.categoryVoucher": "Kategoriegutschein",
234
+ "voucher.selectCategory": "Kategorie wählen",
235
+ "voucher.categoryLabel": "Kategorie",
236
+ "voucher.selectCategoryPlaceholder": "Kategorie auswählen...",
237
+ "voucher.categoryAmount": "Gutscheinwert",
227
238
  "voucher.selectAmount": "Betrag wählen",
228
239
  "voucher.customAmount": "Oder eigenen Betrag eingeben",
229
240
  "voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
@@ -477,6 +488,10 @@ const en = {
477
488
  "booking.stepUpsells": "Extras",
478
489
  "booking.stepDetails": "Details",
479
490
  "booking.stepPayment": "Payment",
491
+ "booking.steps": "{{count}} step(s)",
492
+ "booking.freeformUnitsAvailable": "{{count}} units available",
493
+ "booking.freeformNotAvailable": "Selected window is not available",
494
+ "booking.freeformMissingConfig": "Missing freeform configuration for this event type.",
480
495
  // Booking summary
481
496
  "summary.title": "Booking Summary",
482
497
  "summary.subtotal": "Subtotal:",
@@ -532,6 +547,13 @@ const en = {
532
547
  "voucher.monetaryTypeDesc": "Choose any amount",
533
548
  "voucher.eventType": "Event Voucher",
534
549
  "voucher.eventTypeDesc": "For a specific experience",
550
+ "voucher.categoryType": "Category Voucher",
551
+ "voucher.categoryTypeDesc": "For any experience in a category",
552
+ "voucher.categoryVoucher": "Category Voucher",
553
+ "voucher.selectCategory": "Select Category",
554
+ "voucher.categoryLabel": "Category",
555
+ "voucher.selectCategoryPlaceholder": "Choose a category...",
556
+ "voucher.categoryAmount": "Voucher Value",
535
557
  "voucher.selectAmount": "Select Amount",
536
558
  "voucher.customAmount": "Or enter custom amount",
537
559
  "voucher.amountRange": "Amount must be between {{min}} and {{max}}",
@@ -719,6 +741,21 @@ const es = {
719
741
  "instances.bestPrice": "¡¡¡mejor precio!!!",
720
742
  "instances.goodPrice": "buen precio",
721
743
  "instances.oclock": "",
744
+ // Lista de espera
745
+ "waitlist.joinCta": "Lista de espera",
746
+ "waitlist.title": "Apuntarse a la lista de espera",
747
+ "waitlist.subtitle": "{{name}} está agotado. Deja tus datos y te contactaremos si se libera un lugar.",
748
+ "waitlist.name": "Nombre",
749
+ "waitlist.email": "Correo electrónico",
750
+ "waitlist.phone": "Teléfono (opcional)",
751
+ "waitlist.requiredFields": "Por favor, introduce tu nombre y correo electrónico.",
752
+ "waitlist.join": "Apuntarse",
753
+ "waitlist.submitting": "Apuntando...",
754
+ "waitlist.cancel": "Cancelar",
755
+ "waitlist.submitError": "Algo salió mal. Por favor, inténtalo de nuevo.",
756
+ "waitlist.successTitle": "¡Estás en la lista!",
757
+ "waitlist.successMessage": "Eres el número {{position}} en la lista de espera. Te contactaremos si se libera un lugar.",
758
+ "waitlist.done": "Listo",
722
759
  // Next events preview
723
760
  "nextEvents.title": "Próximas fechas disponibles",
724
761
  "nextEvents.subtitle": "Selecciona una fecha o consulta todas las fechas disponibles",
@@ -822,6 +859,13 @@ const es = {
822
859
  "voucher.monetaryTypeDesc": "Elige cualquier cantidad",
823
860
  "voucher.eventType": "Vale de experiencia",
824
861
  "voucher.eventTypeDesc": "Para una experiencia específica",
862
+ "voucher.categoryType": "Vale de categoría",
863
+ "voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
864
+ "voucher.categoryVoucher": "Vale de categoría",
865
+ "voucher.selectCategory": "Seleccionar categoría",
866
+ "voucher.categoryLabel": "Categoría",
867
+ "voucher.selectCategoryPlaceholder": "Elige una categoría...",
868
+ "voucher.categoryAmount": "Valor del vale",
825
869
  "voucher.selectAmount": "Selecciona el importe",
826
870
  "voucher.customAmount": "O introduce un importe personalizado",
827
871
  "voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
@@ -1009,6 +1053,21 @@ const pt = {
1009
1053
  "instances.bestPrice": "melhor preço !!!",
1010
1054
  "instances.goodPrice": "bom preço",
1011
1055
  "instances.oclock": "",
1056
+ // Lista de espera
1057
+ "waitlist.joinCta": "Lista de espera",
1058
+ "waitlist.title": "Entrar na lista de espera",
1059
+ "waitlist.subtitle": "{{name}} está esgotado. Deixe os seus dados e entraremos em contacto se surgir uma vaga.",
1060
+ "waitlist.name": "Nome",
1061
+ "waitlist.email": "E-mail",
1062
+ "waitlist.phone": "Telefone (opcional)",
1063
+ "waitlist.requiredFields": "Por favor, introduza o seu nome e e-mail.",
1064
+ "waitlist.join": "Inscrever-se",
1065
+ "waitlist.submitting": "A inscrever...",
1066
+ "waitlist.cancel": "Cancelar",
1067
+ "waitlist.submitError": "Algo correu mal. Por favor, tente novamente.",
1068
+ "waitlist.successTitle": "Está na lista!",
1069
+ "waitlist.successMessage": "É o número {{position}} na lista de espera. Entraremos em contacto se surgir uma vaga.",
1070
+ "waitlist.done": "Concluído",
1012
1071
  // Next events preview
1013
1072
  "nextEvents.title": "Próximas datas disponíveis",
1014
1073
  "nextEvents.subtitle": "Selecione uma data ou veja todas as datas disponíveis",
@@ -1112,6 +1171,13 @@ const pt = {
1112
1171
  "voucher.monetaryTypeDesc": "Escolha qualquer quantia",
1113
1172
  "voucher.eventType": "Vale de experiência",
1114
1173
  "voucher.eventTypeDesc": "Para uma experiência específica",
1174
+ "voucher.categoryType": "Vale de categoria",
1175
+ "voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
1176
+ "voucher.categoryVoucher": "Vale de categoria",
1177
+ "voucher.selectCategory": "Selecionar categoria",
1178
+ "voucher.categoryLabel": "Categoria",
1179
+ "voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
1180
+ "voucher.categoryAmount": "Valor do vale",
1115
1181
  "voucher.selectAmount": "Selecione o valor",
1116
1182
  "voucher.customAmount": "Ou introduza um valor personalizado",
1117
1183
  "voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
@@ -1299,6 +1365,21 @@ const sv = {
1299
1365
  "instances.bestPrice": "bästa pris !!!",
1300
1366
  "instances.goodPrice": "bra pris",
1301
1367
  "instances.oclock": "",
1368
+ // Väntelista
1369
+ "waitlist.joinCta": "Ställ dig i kö",
1370
+ "waitlist.title": "Ställ dig i kö",
1371
+ "waitlist.subtitle": "{{name}} är fullbokat. Lämna dina uppgifter så kontaktar vi dig om en plats blir ledig.",
1372
+ "waitlist.name": "Namn",
1373
+ "waitlist.email": "E-post",
1374
+ "waitlist.phone": "Telefon (valfritt)",
1375
+ "waitlist.requiredFields": "Ange ditt namn och din e-postadress.",
1376
+ "waitlist.join": "Ställ dig i kö",
1377
+ "waitlist.submitting": "Anmäler...",
1378
+ "waitlist.cancel": "Avbryt",
1379
+ "waitlist.submitError": "Något gick fel. Försök igen.",
1380
+ "waitlist.successTitle": "Du står i kön!",
1381
+ "waitlist.successMessage": "Du är nummer {{position}} på väntelistan. Vi hör av oss om en plats blir ledig.",
1382
+ "waitlist.done": "Klar",
1302
1383
  // Next events preview
1303
1384
  "nextEvents.title": "Nästa tillgängliga datum",
1304
1385
  "nextEvents.subtitle": "Välj ett datum eller visa alla tillgängliga datum",
@@ -1402,6 +1483,13 @@ const sv = {
1402
1483
  "voucher.monetaryTypeDesc": "Välj valfritt belopp",
1403
1484
  "voucher.eventType": "Upplevelsebevis",
1404
1485
  "voucher.eventTypeDesc": "För en specifik upplevelse",
1486
+ "voucher.categoryType": "Kategoripresent",
1487
+ "voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
1488
+ "voucher.categoryVoucher": "Kategoripresent",
1489
+ "voucher.selectCategory": "Välj kategori",
1490
+ "voucher.categoryLabel": "Kategori",
1491
+ "voucher.selectCategoryPlaceholder": "Välj en kategori...",
1492
+ "voucher.categoryAmount": "Presentkortsvärde",
1405
1493
  "voucher.selectAmount": "Välj belopp",
1406
1494
  "voucher.customAmount": "Eller ange eget belopp",
1407
1495
  "voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
@@ -6372,7 +6460,7 @@ function loadMollieScript() {
6372
6460
  document.head.appendChild(script);
6373
6461
  });
6374
6462
  }
6375
- function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
6463
+ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
6376
6464
  const t = useTranslations();
6377
6465
  const { locale } = useLocale();
6378
6466
  const [isLoading, setIsLoading] = React.useState(false);
@@ -6386,7 +6474,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6386
6474
  const cardFormRef = React.useRef(null);
6387
6475
  const cardContainerRef = React.useRef(null);
6388
6476
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6389
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6477
+ const isFullyCoveredByGiftCards = freeformSelection
6478
+ ? false
6479
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6390
6480
  const isCreditCard = selectedMethod === "creditcard";
6391
6481
  React.useEffect(() => {
6392
6482
  if (isFullyCoveredByGiftCards)
@@ -6496,6 +6586,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6496
6586
  return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
6497
6587
  }
6498
6588
  const validateBeforePayment = () => {
6589
+ if (freeformSelection) {
6590
+ return null;
6591
+ }
6499
6592
  const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
6500
6593
  const availableSpots = eventDetails.availableSpots || 0;
6501
6594
  if (participantCount > availableSpots) {
@@ -6507,21 +6600,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6507
6600
  return null;
6508
6601
  };
6509
6602
  const buildRequestData = (cardToken) => ({
6510
- eventInstanceId: config.eventInstanceId || eventDetails.id,
6511
- organizationId: config.organizationId,
6512
- amount: Math.round(totalAmount),
6513
- currency: "EUR",
6514
- participants: formData.participants.filter((p) => p.name?.trim()),
6515
- discountCode: discountCode?.code,
6516
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6517
- customerName: formData.customerName?.trim(),
6518
- customerEmail: formData.customerEmail?.trim(),
6519
- customerPhone: formData.customerPhone?.trim(),
6520
- comment: formData.comment?.trim(),
6521
- ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6522
- ...(cardToken && { cardToken }),
6523
- ...(selectedMethod && !cardToken && { method: selectedMethod }),
6524
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6603
+ ...(freeformSelection
6604
+ ? {
6605
+ eventTypeId: freeformSelection.eventTypeId,
6606
+ organizationId: config.organizationId,
6607
+ start: freeformSelection.start,
6608
+ end: freeformSelection.end,
6609
+ requestedUnits: freeformSelection.requestedUnits,
6610
+ amount: Math.round(totalAmount),
6611
+ currency: "EUR",
6612
+ customerName: freeformSelection.customerName.trim(),
6613
+ customerEmail: freeformSelection.customerEmail.trim(),
6614
+ customerPhone: freeformSelection.customerPhone?.trim(),
6615
+ comment: freeformSelection.comment?.trim(),
6616
+ locale,
6617
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6618
+ ...(cardToken && { cardToken }),
6619
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6620
+ }
6621
+ : {
6622
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
6623
+ organizationId: config.organizationId,
6624
+ amount: Math.round(totalAmount),
6625
+ currency: "EUR",
6626
+ participants: formData.participants.filter((p) => p.name?.trim()),
6627
+ discountCode: discountCode?.code,
6628
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6629
+ customerName: formData.customerName?.trim(),
6630
+ customerEmail: formData.customerEmail?.trim(),
6631
+ customerPhone: formData.customerPhone?.trim(),
6632
+ comment: formData.comment?.trim(),
6633
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6634
+ ...(cardToken && { cardToken }),
6635
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6636
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6637
+ }),
6525
6638
  });
6526
6639
  const handleCardPayment = async () => {
6527
6640
  if (!mollieRef.current)
@@ -6539,7 +6652,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6539
6652
  setPaymentError(tokenError?.message || t("error.createPayment"));
6540
6653
  return;
6541
6654
  }
6542
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6655
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6656
+ ? "/booking/create-freeform-mollie-payment"
6657
+ : "/booking/create-mollie-payment"), {
6543
6658
  method: "POST",
6544
6659
  headers: createApiHeaders(config, locale),
6545
6660
  body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
@@ -6575,7 +6690,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6575
6690
  setPaymentError(validationError);
6576
6691
  return;
6577
6692
  }
6578
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6693
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6694
+ ? "/booking/create-freeform-mollie-payment"
6695
+ : "/booking/create-mollie-payment"), {
6579
6696
  method: "POST",
6580
6697
  headers: createApiHeaders(config, locale),
6581
6698
  body: JSON.stringify(createRequestBody(config, buildRequestData())),
@@ -6603,7 +6720,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6603
6720
  ? t("button.processingPayment")
6604
6721
  : selectedMethodData
6605
6722
  ? t("payment.payWithMethod", { method: selectedMethodData.description })
6606
- : totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6723
+ : !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6607
6724
  ? t("button.depositAndBook")
6608
6725
  : t("button.bookNow");
6609
6726
  const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
@@ -6910,12 +7027,18 @@ var loadStripe = function loadStripe() {
6910
7027
  };
6911
7028
 
6912
7029
  // Inner component that uses the Stripe hooks
6913
- function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
7030
+ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
6914
7031
  const t = useTranslations();
6915
- const participantCount = formData.participants.filter((p) => p.name.trim()).length;
6916
- const fullAmount = eventDetails.price * participantCount;
6917
- const submitLabel = totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
7032
+ const isFreeform = Boolean(freeformSelection);
7033
+ const participantCount = isFreeform
7034
+ ? 1
7035
+ : formData.participants.filter((p) => p.name.trim()).length;
7036
+ const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
7037
+ const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
6918
7038
  return (jsxRuntime.jsx(StripeElementsPaymentForm, { onValidate: () => {
7039
+ if (isFreeform) {
7040
+ return null;
7041
+ }
6919
7042
  const availableSpots = eventDetails.availableSpots || 0;
6920
7043
  if (participantCount > availableSpots) {
6921
7044
  return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
@@ -6937,7 +7060,7 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
6937
7060
  },
6938
7061
  }, submitContent: jsxRuntime.jsx(jsxRuntime.Fragment, { children: submitLabel }), loadingContent: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
6939
7062
  }
6940
- function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
7063
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
6941
7064
  const t = useTranslations();
6942
7065
  const { locale } = useLocale();
6943
7066
  const [clientSecret, setClientSecret] = React.useState(null);
@@ -6957,7 +7080,9 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6957
7080
  return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
6958
7081
  }, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
6959
7082
  const storageKey = typeof window !== "undefined"
6960
- ? `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
7083
+ ? freeformSelection
7084
+ ? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
7085
+ : `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
6961
7086
  : "";
6962
7087
  const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
6963
7088
  function loadPersistedPaymentIntent() {
@@ -7012,48 +7137,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7012
7137
  }, [paymentIntentId]);
7013
7138
  React.useEffect(() => {
7014
7139
  const createStripePayment = async () => {
7015
- if (!systemConfig || !eventDetails || !formData.participants?.length) {
7140
+ if (!systemConfig || !eventDetails) {
7141
+ return;
7142
+ }
7143
+ if (freeformSelection) {
7144
+ if (!freeformSelection.customerEmail?.trim() ||
7145
+ !freeformSelection.customerName?.trim() ||
7146
+ !freeformSelection.start ||
7147
+ !freeformSelection.end) {
7148
+ return;
7149
+ }
7150
+ }
7151
+ else if (!formData.participants?.length) {
7016
7152
  return;
7017
7153
  }
7018
7154
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
7019
- if (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim()) {
7155
+ if (!freeformSelection &&
7156
+ (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
7020
7157
  return;
7021
7158
  }
7022
7159
  setIsCreatingPaymentIntent(true);
7023
7160
  setPaymentError(null);
7024
7161
  try {
7025
- const requestData = {
7026
- eventInstanceId: config.eventInstanceId || eventDetails.id,
7027
- organizationId: config.organizationId,
7028
- amount: Math.round(totalAmount),
7029
- currency: "eur",
7030
- participants: formData.participants.filter((p) => p.name?.trim()),
7031
- discountCode: discountCode?.code,
7032
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7033
- customerName: formData.customerName?.trim(),
7034
- customerEmail: formData.customerEmail?.trim(),
7035
- customerPhone: formData.customerPhone?.trim(),
7036
- comment: formData.comment?.trim(),
7037
- ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7038
- ...(paymentIntentId && { paymentIntentId }),
7039
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7040
- };
7041
- if (!requestData.eventInstanceId) {
7042
- throw new Error("Event instance ID is required");
7043
- }
7162
+ const endpoint = freeformSelection
7163
+ ? "/booking/create-freeform-stripe-payment"
7164
+ : "/booking/create-stripe-payment";
7165
+ const requestData = freeformSelection
7166
+ ? {
7167
+ eventTypeId: freeformSelection.eventTypeId,
7168
+ organizationId: config.organizationId,
7169
+ start: freeformSelection.start,
7170
+ end: freeformSelection.end,
7171
+ requestedUnits: freeformSelection.requestedUnits,
7172
+ amount: Math.round(totalAmount),
7173
+ currency: "eur",
7174
+ customerName: freeformSelection.customerName.trim(),
7175
+ customerEmail: freeformSelection.customerEmail.trim(),
7176
+ customerPhone: freeformSelection.customerPhone?.trim(),
7177
+ comment: freeformSelection.comment?.trim(),
7178
+ locale,
7179
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7180
+ ...(paymentIntentId && { paymentIntentId }),
7181
+ }
7182
+ : {
7183
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
7184
+ organizationId: config.organizationId,
7185
+ amount: Math.round(totalAmount),
7186
+ currency: "eur",
7187
+ participants: formData.participants.filter((p) => p.name?.trim()),
7188
+ discountCode: discountCode?.code,
7189
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7190
+ customerName: formData.customerName?.trim(),
7191
+ customerEmail: formData.customerEmail?.trim(),
7192
+ customerPhone: formData.customerPhone?.trim(),
7193
+ comment: formData.comment?.trim(),
7194
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7195
+ ...(paymentIntentId && { paymentIntentId }),
7196
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7197
+ };
7044
7198
  if (!requestData.organizationId) {
7045
7199
  throw new Error("Organization ID is required");
7046
7200
  }
7047
- if (!requestData.amount || requestData.amount <= 0) {
7201
+ if (requestData.amount === undefined || requestData.amount < 0) {
7048
7202
  throw new Error("Valid amount is required");
7049
7203
  }
7050
- if (!requestData.participants || requestData.participants.length === 0) {
7204
+ if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
7205
+ throw new Error("Event instance ID is required");
7206
+ }
7207
+ if (!freeformSelection &&
7208
+ "participants" in requestData &&
7209
+ (!requestData.participants || requestData.participants.length === 0)) {
7051
7210
  throw new Error("At least one participant is required");
7052
7211
  }
7053
7212
  if (!requestData.customerEmail) {
7054
7213
  throw new Error("Customer email is required");
7055
7214
  }
7056
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
7215
+ const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
7057
7216
  method: "POST",
7058
7217
  headers: createApiHeaders(config, locale),
7059
7218
  body: JSON.stringify(createRequestBody(config, requestData)),
@@ -7098,9 +7257,13 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7098
7257
  giftCards,
7099
7258
  config,
7100
7259
  upsellSelections,
7260
+ freeformSelection,
7261
+ locale,
7101
7262
  ]);
7102
7263
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
7103
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7264
+ const isFullyCoveredByGiftCards = freeformSelection
7265
+ ? false
7266
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7104
7267
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
7105
7268
  return (jsxRuntime.jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
7106
7269
  }
@@ -7139,7 +7302,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7139
7302
  setPaymentIntentId(null);
7140
7303
  setClientSecret(null);
7141
7304
  onSuccess(result);
7142
- }, onError: onError }) }));
7305
+ }, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
7143
7306
  }
7144
7307
 
7145
7308
  const HOLD_DURATION_SECONDS = 15 * 60;
@@ -13399,20 +13562,22 @@ const sectionHeaderStyles = sectionStyles.header;
13399
13562
  const labelStyles = formStyles.label;
13400
13563
  const inputStyles = formStyles.input;
13401
13564
  const errorTextStyles = formStyles.error;
13402
- function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13565
+ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13403
13566
  const t = useTranslations();
13404
13567
  const { locale } = useLocale();
13405
13568
  const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
13406
13569
  // Form state
13407
- // Show voucher type selection if both monetary and event vouchers are allowed
13408
- // The event type selection will handle the case when no event types are available
13409
13570
  const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
13410
13571
  const canUseEventVoucherType = voucherConfig.allowEventVouchers;
13411
- const showVoucherTypeSelection = canUseMonetaryVoucherType && canUseEventVoucherType && !isLoadingEventTypes;
13572
+ const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
13573
+ const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
13574
+ const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
13412
13575
  const [voucherType, setVoucherType] = React.useState(canUseMonetaryVoucherType ? "monetary" : "event");
13413
13576
  const [monetaryAmount, setMonetaryAmount] = React.useState(voucherConfig.monetaryPresets[2] || 10000);
13414
13577
  const [selectedEventTypeId, setSelectedEventTypeId] = React.useState(null);
13415
13578
  const [eventQuantity, setEventQuantity] = React.useState(1);
13579
+ const [selectedCategoryId, setSelectedCategoryId] = React.useState(null);
13580
+ const [categoryAmount, setCategoryAmount] = React.useState(10000);
13416
13581
  const [recipientName, setRecipientName] = React.useState("");
13417
13582
  const [message, setMessage] = React.useState("");
13418
13583
  const [customerName, setCustomerName] = React.useState("");
@@ -13472,6 +13637,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13472
13637
  if (voucherType === "monetary") {
13473
13638
  return monetaryAmount;
13474
13639
  }
13640
+ else if (voucherType === "category") {
13641
+ return categoryAmount;
13642
+ }
13475
13643
  else {
13476
13644
  const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
13477
13645
  if (eventType) {
@@ -13479,7 +13647,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13479
13647
  }
13480
13648
  return 0;
13481
13649
  }
13482
- }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
13650
+ }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
13483
13651
  const selectedEventType = React.useMemo(() => {
13484
13652
  return eventTypes.find((et) => et.id === selectedEventTypeId);
13485
13653
  }, [eventTypes, selectedEventTypeId]);
@@ -13488,6 +13656,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13488
13656
  voucherType,
13489
13657
  selectedEventTypeId,
13490
13658
  eventQuantity,
13659
+ selectedCategoryId,
13660
+ categoryAmount,
13491
13661
  recipientName: recipientName.trim(),
13492
13662
  message: message.trim(),
13493
13663
  customerName: customerName.trim(),
@@ -13497,6 +13667,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13497
13667
  voucherType,
13498
13668
  selectedEventTypeId,
13499
13669
  eventQuantity,
13670
+ selectedCategoryId,
13671
+ categoryAmount,
13500
13672
  recipientName,
13501
13673
  message,
13502
13674
  customerName,
@@ -13515,24 +13687,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13515
13687
  return false;
13516
13688
  if (voucherType === "event" && !selectedEventTypeId)
13517
13689
  return false;
13690
+ if (voucherType === "category" && !selectedCategoryId)
13691
+ return false;
13518
13692
  return true;
13519
- }, [customerName, customerEmail, acceptTerms, totalAmount, voucherConfig, voucherType, selectedEventTypeId]);
13693
+ }, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
13520
13694
  const defaultMonetaryAmount = React.useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
13521
13695
  const handleVoucherTypeChange = React.useCallback((nextType) => {
13522
13696
  setVoucherType(nextType);
13523
13697
  setPaymentError(null);
13524
13698
  if (nextType === "event") {
13525
- // Keep voucher type mutually exclusive: entering event flow resets amount selection state.
13526
13699
  setMonetaryAmount(defaultMonetaryAmount);
13700
+ setSelectedCategoryId(null);
13701
+ setCategoryAmount(10000);
13527
13702
  if (eventTypes.length === 1) {
13528
13703
  setSelectedEventTypeId(eventTypes[0]?.id ?? null);
13529
13704
  }
13530
13705
  return;
13531
13706
  }
13532
- // Keep voucher type mutually exclusive: entering amount flow clears event selection.
13707
+ if (nextType === "category") {
13708
+ setMonetaryAmount(defaultMonetaryAmount);
13709
+ setSelectedEventTypeId(null);
13710
+ setEventQuantity(1);
13711
+ if (categories.length === 1) {
13712
+ setSelectedCategoryId(categories[0]?.id ?? null);
13713
+ }
13714
+ return;
13715
+ }
13533
13716
  setSelectedEventTypeId(null);
13534
13717
  setEventQuantity(1);
13535
- }, [defaultMonetaryAmount, eventTypes]);
13718
+ setSelectedCategoryId(null);
13719
+ setCategoryAmount(10000);
13720
+ }, [defaultMonetaryAmount, eventTypes, categories]);
13536
13721
  React.useEffect(() => {
13537
13722
  if (!canUseMonetaryVoucherType && voucherType === "monetary") {
13538
13723
  setVoucherType("event");
@@ -13573,9 +13758,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13573
13758
  organizationId: config.organizationId,
13574
13759
  amount: totalAmount,
13575
13760
  currency: "eur",
13576
- voucherType,
13577
- monetaryValue: voucherType === "monetary" ? totalAmount : undefined,
13761
+ voucherType: voucherType === "category" ? "monetary" : voucherType,
13762
+ monetaryValue: voucherType === "monetary" ? totalAmount
13763
+ : voucherType === "category" ? categoryAmount
13764
+ : undefined,
13578
13765
  eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
13766
+ eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
13579
13767
  eventAmount: voucherType === "event" ? eventQuantity : undefined,
13580
13768
  recipientName: recipientName.trim() || undefined,
13581
13769
  message: message.trim() || undefined,
@@ -13766,7 +13954,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13766
13954
  fontSize: "13px",
13767
13955
  color: "var(--bw-text-muted)",
13768
13956
  marginTop: "4px",
13769
- }, children: t("voucher.eventTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsxRuntime.jsx("div", { style: {
13957
+ }, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxRuntime.jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
13958
+ flex: 1,
13959
+ padding: "16px",
13960
+ borderRadius: "var(--bw-border-radius)",
13961
+ border: voucherType === "category"
13962
+ ? "2px solid var(--bw-highlight-color)"
13963
+ : "1px solid var(--bw-border-color)",
13964
+ backgroundColor: voucherType === "category"
13965
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
13966
+ : "var(--bw-surface-color)",
13967
+ cursor: "pointer",
13968
+ fontFamily: "var(--bw-font-family)",
13969
+ transition: "all 0.2s ease",
13970
+ textAlign: "center",
13971
+ }, children: [jsxRuntime.jsx("div", { style: {
13972
+ fontWeight: 600,
13973
+ color: voucherType === "category"
13974
+ ? "var(--bw-highlight-color)"
13975
+ : "var(--bw-text-color)",
13976
+ }, children: t("voucher.categoryType") }), jsxRuntime.jsx("div", { style: {
13977
+ fontSize: "13px",
13978
+ color: "var(--bw-text-muted)",
13979
+ marginTop: "4px",
13980
+ }, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsxRuntime.jsx("div", { style: {
13770
13981
  display: "grid",
13771
13982
  gridTemplateColumns: "repeat(3, 1fr)",
13772
13983
  gap: "8px",
@@ -13821,7 +14032,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13821
14032
  fontSize: "18px",
13822
14033
  color: "var(--bw-highlight-color)",
13823
14034
  fontFamily: "var(--bw-font-family)",
13824
- }, children: formatCurrency(totalAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsxRuntime.jsx("p", { style: {
14035
+ }, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), jsxRuntime.jsx("div", { style: { marginTop: "12px" }, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), jsxRuntime.jsxs("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
14036
+ ...inputStyles,
14037
+ cursor: categories.length === 1 ? "not-allowed" : "pointer",
14038
+ }, children: [categories.length > 1 && (jsxRuntime.jsx("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (jsxRuntime.jsx("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), jsxRuntime.jsx("div", { style: {
14039
+ display: "grid",
14040
+ gridTemplateColumns: "repeat(3, 1fr)",
14041
+ gap: "8px",
14042
+ marginTop: "4px",
14043
+ }, children: voucherConfig.monetaryPresets.map((preset) => (jsxRuntime.jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
14044
+ padding: "10px",
14045
+ borderRadius: "var(--bw-border-radius)",
14046
+ border: categoryAmount === preset
14047
+ ? "2px solid var(--bw-highlight-color)"
14048
+ : "1px solid var(--bw-border-color)",
14049
+ backgroundColor: categoryAmount === preset
14050
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
14051
+ : "var(--bw-surface-color)",
14052
+ cursor: "pointer",
14053
+ fontFamily: "var(--bw-font-family)",
14054
+ fontWeight: 600,
14055
+ fontSize: "14px",
14056
+ color: categoryAmount === preset
14057
+ ? "var(--bw-highlight-color)"
14058
+ : "var(--bw-text-color)",
14059
+ transition: "all 0.2s ease",
14060
+ }, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsxRuntime.jsx("div", { style: {
14061
+ marginTop: "16px",
14062
+ padding: "12px",
14063
+ backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
14064
+ borderRadius: "var(--bw-border-radius)",
14065
+ border: "1px solid var(--bw-highlight-color)",
14066
+ }, children: jsxRuntime.jsxs("div", { style: {
14067
+ display: "flex",
14068
+ justifyContent: "space-between",
14069
+ alignItems: "center",
14070
+ }, children: [jsxRuntime.jsx("span", { style: {
14071
+ color: "var(--bw-text-color)",
14072
+ fontFamily: "var(--bw-font-family)",
14073
+ }, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsxRuntime.jsx("span", { style: {
14074
+ fontWeight: 700,
14075
+ fontSize: "18px",
14076
+ color: "var(--bw-highlight-color)",
14077
+ fontFamily: "var(--bw-font-family)",
14078
+ }, children: formatCurrency(categoryAmount) })] }) }))] })), jsxRuntime.jsxs("div", { style: cardStyles, children: [jsxRuntime.jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsxRuntime.jsx("p", { style: {
13825
14079
  fontSize: "13px",
13826
14080
  color: "var(--bw-text-muted)",
13827
14081
  fontFamily: "var(--bw-font-family)",
@@ -13854,7 +14108,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13854
14108
  fontFamily: "var(--bw-font-family)",
13855
14109
  }, children: voucherType === "monetary"
13856
14110
  ? t("voucher.monetaryVoucher")
13857
- : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxRuntime.jsxs("div", { style: {
14111
+ : voucherType === "category"
14112
+ ? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
14113
+ : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxRuntime.jsxs("div", { style: {
13858
14114
  fontSize: "13px",
13859
14115
  color: "var(--bw-text-muted)",
13860
14116
  fontFamily: "var(--bw-font-family)",
@@ -14130,7 +14386,7 @@ function VoucherAttachment({ onClick }) {
14130
14386
  }, children: t("voucher.buyAsGift") })] }));
14131
14387
  }
14132
14388
 
14133
- function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14389
+ function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14134
14390
  if (!voucherConfig?.enabled) {
14135
14391
  return null;
14136
14392
  }
@@ -14141,7 +14397,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
14141
14397
  justifyContent: "center",
14142
14398
  }, children: jsxRuntime.jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
14143
14399
  ? Math.min(...eventTypes.map((et) => et.maxPrice))
14144
- : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14400
+ : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsxRuntime.jsx(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 && (jsxRuntime.jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14145
14401
  }
14146
14402
 
14147
14403
  // Helper function to preprocess markdown for underline support
@@ -14201,7 +14457,7 @@ function InfoBadge({ text }) {
14201
14457
  pointerEvents: "none",
14202
14458
  }, children: text }))] }));
14203
14459
  }
14204
- function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
14460
+ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, showImages = true, }) {
14205
14461
  const t = useTranslations();
14206
14462
  const { locale } = useLocale();
14207
14463
  const timezone = useTimezone();
@@ -14280,6 +14536,18 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14280
14536
  gridAutoRows: "1fr",
14281
14537
  }, children: eventTypes.map((eventType) => {
14282
14538
  const isAvailable = eventType.hasAvailableInstances;
14539
+ const hasImage = showImages && eventType.images && eventType.images.length > 0;
14540
+ const availabilityBadge = (jsxRuntime.jsx("div", { style: {
14541
+ padding: "4px 8px",
14542
+ borderRadius: "var(--bw-border-radius-small)",
14543
+ fontSize: "12px",
14544
+ fontWeight: 600,
14545
+ color: "#ffffff",
14546
+ fontFamily: "var(--bw-font-family)",
14547
+ backgroundColor: isAvailable
14548
+ ? "var(--bw-success-color)"
14549
+ : "var(--bw-error-color)",
14550
+ }, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }));
14283
14551
  return (jsxRuntime.jsxs("div", { className: "event-type-card", style: {
14284
14552
  position: "relative",
14285
14553
  backgroundColor: "var(--bw-surface-color)",
@@ -14291,17 +14559,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14291
14559
  fontFamily: "var(--bw-font-family)",
14292
14560
  cursor: isAvailable ? "pointer" : "not-allowed",
14293
14561
  opacity: isAvailable ? 1 : 0.6,
14294
- }, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: jsxRuntime.jsx("div", { style: {
14295
- padding: "4px 8px",
14296
- borderRadius: "var(--bw-border-radius-small)",
14297
- fontSize: "12px",
14298
- fontWeight: 600,
14299
- color: "#ffffff",
14300
- fontFamily: "var(--bw-font-family)",
14301
- backgroundColor: isAvailable
14302
- ? "var(--bw-success-color)"
14303
- : "var(--bw-error-color)",
14304
- }, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }) }), eventType.images && eventType.images.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsxRuntime.jsx("div", { style: {
14562
+ }, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [hasImage && (jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: availabilityBadge })), hasImage && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsxRuntime.jsx("div", { style: {
14305
14563
  fontSize: "13px",
14306
14564
  color: "var(--bw-surface-color)",
14307
14565
  fontWeight: 600,
@@ -14315,7 +14573,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14315
14573
  flexDirection: "column",
14316
14574
  justifyContent: "space-between",
14317
14575
  height: "540px",
14318
- }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("h2", { className: "event-type-title", style: {
14576
+ }, children: [jsxRuntime.jsxs("div", { children: [!hasImage && (jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "flex-end", margin: "0 0 8px 0" }, children: availabilityBadge })), jsxRuntime.jsx("h2", { className: "event-type-title", style: {
14319
14577
  fontSize: "clamp(1.1rem, 2.5vw, 24px)",
14320
14578
  fontWeight: 700,
14321
14579
  color: "var(--bw-text-color)",
@@ -14587,16 +14845,16 @@ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName
14587
14845
  }
14588
14846
 
14589
14847
  const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
14590
- const availabilityRatio = availableSpots / maxParticipants;
14591
- if (availabilityRatio >= 0.6)
14592
- return null;
14593
- if (availabilityRatio === 0) {
14848
+ if (maxParticipants <= 0 || availableSpots <= 0) {
14594
14849
  return {
14595
14850
  text: t("events.soldOut"),
14596
14851
  backgroundColor: "#7f1d1d",
14597
14852
  textColor: "#fca5a5",
14598
14853
  };
14599
14854
  }
14855
+ const availabilityRatio = availableSpots / maxParticipants;
14856
+ if (availabilityRatio >= 0.6)
14857
+ return null;
14600
14858
  if (availabilityRatio <= 0.3) {
14601
14859
  return {
14602
14860
  text: t("instances.almostSoldOut"),
@@ -14710,7 +14968,7 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
14710
14968
  boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
14711
14969
  }, children: formatCurrency(price) }));
14712
14970
  };
14713
- function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
14971
+ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, waitlistEnabled = false, }) {
14714
14972
  const t = useTranslations();
14715
14973
  const { locale } = useLocale();
14716
14974
  const timezone = useTimezone();
@@ -14895,12 +15153,13 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14895
15153
  ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14896
15154
  : undefined,
14897
15155
  }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14898
- const availableSpots = event.maxParticipants - event.participantCount;
14899
- const isFullyBooked = availableSpots === 0;
15156
+ const availableSpots = event.availableSpots;
14900
15157
  const startDate = new Date(event.startTime);
14901
15158
  const isPastEvent = today.toISOString() >= startDate.toISOString();
14902
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14903
- const canWaitlist = isFullyBooked &&
15159
+ const isUnavailable = availableSpots <= 0 || !event.bookingOpen;
15160
+ const isDisabled = isUnavailable || isPastEvent;
15161
+ const canWaitlist = waitlistEnabled &&
15162
+ isUnavailable &&
14904
15163
  !isPastEvent &&
14905
15164
  !!apiBaseUrl &&
14906
15165
  !!organizationId;
@@ -14937,7 +15196,7 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14937
15196
  color: "var(--bw-highlight-color)",
14938
15197
  opacity: 0.8,
14939
15198
  fontSize: "32px",
14940
- }, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxRuntime.jsxs("div", { style: {
15199
+ }, children: spinner() }) })), jsxRuntime.jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsxRuntime.jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.effectiveMaxParticipants ?? event.maxParticipants, t: t }), jsxRuntime.jsxs("div", { style: {
14941
15200
  display: "flex",
14942
15201
  justifyContent: "space-between",
14943
15202
  width: "100%",
@@ -15114,18 +15373,19 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
15114
15373
  margin: "0 auto",
15115
15374
  padding: "12px 16px",
15116
15375
  fontFamily: "var(--bw-font-family)",
15117
- }, children: [jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events.map((event) => {
15118
- const availableSpots = event.maxParticipants - event.participantCount;
15119
- const isFullyBooked = availableSpots === 0;
15376
+ }, children: [jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events
15377
+ .filter((event) => {
15120
15378
  const startDate = new Date(event.startTime);
15121
15379
  const isPastEvent = today.toISOString() >= startDate.toISOString();
15122
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
15123
- const availabilityRatio = availableSpots / event.maxParticipants;
15380
+ return !isPastEvent && event.availableSpots > 0 && event.bookingOpen;
15381
+ })
15382
+ .map((event) => {
15383
+ const availableSpots = event.availableSpots;
15384
+ const startDate = new Date(event.startTime);
15124
15385
  const allocationBadge = (() => {
15386
+ const availabilityRatio = availableSpots / event.maxParticipants;
15125
15387
  if (availabilityRatio >= 0.6)
15126
15388
  return null;
15127
- if (availabilityRatio === 0)
15128
- return { text: t("events.soldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
15129
15389
  if (availabilityRatio <= 0.3)
15130
15390
  return { text: t("instances.almostSoldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
15131
15391
  return { text: t("instances.popularDate"), backgroundColor: "#b45309", textColor: "#fbbf24" };
@@ -15139,13 +15399,9 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
15139
15399
  padding: "16px 10px",
15140
15400
  transition: "all 0.2s ease",
15141
15401
  fontFamily: "var(--bw-font-family)",
15142
- opacity: isDisabled ? 0.3 : 1,
15143
- filter: isDisabled ? "grayscale(40%)" : "none",
15144
- cursor: isDisabled ? "not-allowed" : "pointer",
15402
+ cursor: "pointer",
15145
15403
  }, onClick: () => {
15146
- if (!isDisabled) {
15147
- handleEventSelect(event.id);
15148
- }
15404
+ handleEventSelect(event.id);
15149
15405
  }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
15150
15406
  position: "absolute",
15151
15407
  inset: 0,
@@ -15400,7 +15656,7 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
15400
15656
  alignItems: "center",
15401
15657
  backgroundColor: "var(--bw-background-color)",
15402
15658
  }, children: [jsxRuntime.jsx("span", { style: { fontSize: "12px", color: "var(--bw-text-muted)" }, children: isFullyBooked
15403
- ? t("common.fullyBooked")
15659
+ ? t("events.soldOut")
15404
15660
  : t("specials.spotsLeft").replace("{{count}}", String(special.availableSpots)) }), jsxRuntime.jsxs("span", { style: {
15405
15661
  fontSize: "13px",
15406
15662
  fontWeight: 600,
@@ -15409,6 +15665,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
15409
15665
  }) })] }));
15410
15666
  }
15411
15667
 
15668
+ function toMinutesForUnit(unit) {
15669
+ switch (unit.toLowerCase()) {
15670
+ case "minute":
15671
+ case "minutes":
15672
+ return 1;
15673
+ case "hour":
15674
+ case "hours":
15675
+ return 60;
15676
+ case "day":
15677
+ case "days":
15678
+ return 24 * 60;
15679
+ default:
15680
+ return 1;
15681
+ }
15682
+ }
15683
+ // --- Timezone helpers ------------------------------------------------------
15684
+ // The booking is evaluated server-side against the organization's local
15685
+ // operating hours. The customer's browser may be in a different timezone, so we
15686
+ // interpret the datetime-local field as a wall-clock time in the ORG timezone
15687
+ // (not the browser's) and convert to a UTC instant for the API.
15688
+ function getTzParts(date, timeZone) {
15689
+ const dtf = new Intl.DateTimeFormat("en-CA", {
15690
+ timeZone,
15691
+ hour12: false,
15692
+ year: "numeric",
15693
+ month: "2-digit",
15694
+ day: "2-digit",
15695
+ hour: "2-digit",
15696
+ minute: "2-digit",
15697
+ second: "2-digit",
15698
+ });
15699
+ const parts = {};
15700
+ for (const part of dtf.formatToParts(date)) {
15701
+ if (part.type !== "literal")
15702
+ parts[part.type] = part.value;
15703
+ }
15704
+ return parts;
15705
+ }
15706
+ function tzOffsetMs(utcMs, timeZone) {
15707
+ const p = getTzParts(new Date(utcMs), timeZone);
15708
+ const hour = p.hour === "24" ? "00" : p.hour;
15709
+ const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
15710
+ return asLocalUtc - utcMs;
15711
+ }
15712
+ /** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
15713
+ function zonedWallTimeToUtc(wallTime, timeZone) {
15714
+ const [datePart, timePart] = wallTime.split("T");
15715
+ if (!datePart || !timePart)
15716
+ return new Date(NaN);
15717
+ const [y, mo, d] = datePart.split("-").map(Number);
15718
+ const [h, mi] = timePart.split(":").map(Number);
15719
+ const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
15720
+ // Single-pass offset correction (good enough outside the ~1h DST fold).
15721
+ const offset = tzOffsetMs(naiveUtc, timeZone);
15722
+ return new Date(naiveUtc - offset);
15723
+ }
15724
+ /** Render a UTC instant as an org-tz datetime-local input value. */
15725
+ function utcToZonedInputValue(date, timeZone) {
15726
+ const p = getTzParts(date, timeZone);
15727
+ const hour = p.hour === "24" ? "00" : p.hour;
15728
+ return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
15729
+ }
15730
+ /** Format a UTC instant for display in the org timezone. */
15731
+ function formatInTimeZone(date, timeZone, locale) {
15732
+ return new Intl.DateTimeFormat(locale, {
15733
+ timeZone,
15734
+ dateStyle: "medium",
15735
+ timeStyle: "short",
15736
+ }).format(date);
15737
+ }
15738
+ function calculateTieredPrice(durationUnits, config) {
15739
+ const tiers = [...(config.tiers ?? [])]
15740
+ .filter((tier) => tier.unitCount > 0 && tier.price >= 0)
15741
+ .sort((a, b) => b.unitCount - a.unitCount);
15742
+ if (tiers.length === 0) {
15743
+ return 0;
15744
+ }
15745
+ let total = 0;
15746
+ let remaining = durationUnits;
15747
+ for (const tier of tiers) {
15748
+ const count = Math.floor(remaining / tier.unitCount);
15749
+ if (count <= 0)
15750
+ continue;
15751
+ total += count * tier.price;
15752
+ remaining -= count * tier.unitCount;
15753
+ }
15754
+ if (remaining > 0) {
15755
+ if (typeof config.perUnitPrice === "number") {
15756
+ total += remaining * config.perUnitPrice;
15757
+ }
15758
+ else {
15759
+ total += tiers[tiers.length - 1]?.price ?? 0;
15760
+ }
15761
+ }
15762
+ return total;
15763
+ }
15764
+ function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
15765
+ const t = useTranslations();
15766
+ const { locale } = useLocale();
15767
+ const freeformConfig = eventType.freeformConfig;
15768
+ const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
15769
+ const isNamedResource = eventType.resourceKind === "named";
15770
+ const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
15771
+ const now = React.useMemo(() => {
15772
+ const next = new Date();
15773
+ next.setMinutes(next.getMinutes() + 30, 0, 0);
15774
+ return next;
15775
+ }, []);
15776
+ const [startInput, setStartInput] = React.useState(utcToZonedInputValue(now, timeZone));
15777
+ const [duration, setDuration] = React.useState(Math.max(1, freeformConfig?.minDuration ?? 1));
15778
+ const [requestedUnits, setRequestedUnits] = React.useState(1);
15779
+ const [customerName, setCustomerName] = React.useState("");
15780
+ const [customerEmail, setCustomerEmail] = React.useState("");
15781
+ const [customerPhone, setCustomerPhone] = React.useState("");
15782
+ const [comment, setComment] = React.useState("");
15783
+ const [acceptTerms, setAcceptTerms] = React.useState(false);
15784
+ const [checkoutStep, setCheckoutStep] = React.useState("details");
15785
+ const [availability, setAvailability] = React.useState(null);
15786
+ const [availabilityError, setAvailabilityError] = React.useState(null);
15787
+ const [isCheckingAvailability, setIsCheckingAvailability] = React.useState(false);
15788
+ React.useEffect(() => {
15789
+ if (!isOpen)
15790
+ return;
15791
+ setCheckoutStep("details");
15792
+ }, [isOpen]);
15793
+ React.useEffect(() => {
15794
+ if (!freeformConfig)
15795
+ return;
15796
+ setDuration(Math.max(1, freeformConfig.minDuration));
15797
+ setRequestedUnits(1);
15798
+ setAvailability(null);
15799
+ setAvailabilityError(null);
15800
+ }, [freeformConfig?.resourceId]);
15801
+ const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
15802
+ const startDate = React.useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
15803
+ const endDate = React.useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
15804
+ React.useEffect(() => {
15805
+ if (!isOpen || !freeformConfig)
15806
+ return;
15807
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
15808
+ return;
15809
+ const timer = setTimeout(async () => {
15810
+ setIsCheckingAvailability(true);
15811
+ setAvailabilityError(null);
15812
+ try {
15813
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
15814
+ method: "POST",
15815
+ headers: createApiHeaders(config, locale),
15816
+ body: JSON.stringify(createRequestBody(config, {
15817
+ eventTypeId: eventType.id,
15818
+ requestedUnits,
15819
+ start: startDate.toISOString(),
15820
+ end: endDate.toISOString(),
15821
+ })),
15822
+ });
15823
+ const data = await response.json();
15824
+ if (!response.ok) {
15825
+ setAvailability(null);
15826
+ setAvailabilityError(data.error || t("error.loadBookingData"));
15827
+ return;
15828
+ }
15829
+ setAvailability({
15830
+ available: Boolean(data.available),
15831
+ freeUnits: Number(data.freeUnits ?? 0),
15832
+ capacityUnits: Number(data.capacityUnits ?? 0),
15833
+ usedUnits: Number(data.usedUnits ?? 0),
15834
+ reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
15835
+ blockUsedUnits: Number(data.blockUsedUnits ?? 0),
15836
+ durationUnits: Number(data.durationUnits ?? 0),
15837
+ roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
15838
+ });
15839
+ }
15840
+ catch (_error) {
15841
+ setAvailability(null);
15842
+ setAvailabilityError(t("error.loadBookingData"));
15843
+ }
15844
+ finally {
15845
+ setIsCheckingAvailability(false);
15846
+ }
15847
+ }, 350);
15848
+ return () => clearTimeout(timer);
15849
+ }, [
15850
+ config,
15851
+ endDate,
15852
+ eventType.id,
15853
+ freeformConfig,
15854
+ isOpen,
15855
+ locale,
15856
+ requestedUnits,
15857
+ startDate,
15858
+ t,
15859
+ ]);
15860
+ const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
15861
+ const basePrice = React.useMemo(() => {
15862
+ if (!freeformConfig)
15863
+ return 0;
15864
+ if (freeformConfig.pricingModel === "perUnit") {
15865
+ return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
15866
+ }
15867
+ return calculateTieredPrice(billedDurationUnits, freeformConfig);
15868
+ }, [billedDurationUnits, freeformConfig]);
15869
+ const totalPrice = Math.max(0, basePrice * requestedUnits);
15870
+ const canContinueToPayment = !!freeformConfig &&
15871
+ customerName.trim().length >= 2 &&
15872
+ customerEmail.trim().length > 3 &&
15873
+ acceptTerms &&
15874
+ !!availability?.available &&
15875
+ !isCheckingAvailability;
15876
+ const freeformSelection = React.useMemo(() => ({
15877
+ eventTypeId: eventType.id,
15878
+ start: startDate.toISOString(),
15879
+ end: endDate.toISOString(),
15880
+ requestedUnits,
15881
+ customerName: customerName.trim(),
15882
+ customerEmail: customerEmail.trim(),
15883
+ customerPhone: customerPhone.trim(),
15884
+ comment: comment.trim(),
15885
+ }), [
15886
+ comment,
15887
+ customerEmail,
15888
+ customerName,
15889
+ customerPhone,
15890
+ endDate,
15891
+ eventType.id,
15892
+ requestedUnits,
15893
+ startDate,
15894
+ ]);
15895
+ const paymentFormData = React.useMemo(() => ({
15896
+ customerName: customerName.trim(),
15897
+ customerEmail: customerEmail.trim(),
15898
+ customerPhone: customerPhone.trim(),
15899
+ participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
15900
+ comment: comment.trim(),
15901
+ }), [comment, customerEmail, customerName, customerPhone]);
15902
+ const eventDetailsForPayment = React.useMemo(() => ({
15903
+ id: eventType.id,
15904
+ name: eventType.name,
15905
+ startTime: startDate.toISOString(),
15906
+ endTime: endDate.toISOString(),
15907
+ price: basePrice,
15908
+ maxParticipants: availability?.capacityUnits ?? requestedUnits,
15909
+ participantCount: 0,
15910
+ availableSpots: availability?.freeUnits ?? requestedUnits,
15911
+ durationDays: 1,
15912
+ durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
15913
+ images: eventType.images ?? [],
15914
+ category: eventType.category,
15915
+ organization: {
15916
+ id: config.organizationId,
15917
+ name: "",
15918
+ },
15919
+ bookingOpen: availability?.available ?? false,
15920
+ }), [
15921
+ availability?.available,
15922
+ availability?.capacityUnits,
15923
+ availability?.freeUnits,
15924
+ availability?.roundedDurationMinutes,
15925
+ basePrice,
15926
+ config.organizationId,
15927
+ duration,
15928
+ endDate,
15929
+ eventType.category,
15930
+ eventType.id,
15931
+ eventType.images,
15932
+ eventType.name,
15933
+ requestedUnits,
15934
+ startDate,
15935
+ unitMinutes,
15936
+ ]);
15937
+ const footer = (jsxRuntime.jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsxRuntime.jsx("button", { type: "button", onClick: () => {
15938
+ if (checkoutStep === "payment") {
15939
+ setCheckoutStep("details");
15940
+ }
15941
+ else {
15942
+ onClose();
15943
+ }
15944
+ }, style: {
15945
+ flex: 1,
15946
+ padding: "12px 16px",
15947
+ border: "1px solid var(--bw-border-color)",
15948
+ borderRadius: "var(--bw-border-radius)",
15949
+ backgroundColor: "var(--bw-surface-color)",
15950
+ color: "var(--bw-text-color)",
15951
+ fontFamily: "var(--bw-font-family)",
15952
+ fontWeight: 600,
15953
+ cursor: "pointer",
15954
+ }, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsxRuntime.jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
15955
+ flex: 1,
15956
+ padding: "12px 16px",
15957
+ border: "none",
15958
+ borderRadius: "var(--bw-border-radius)",
15959
+ backgroundColor: "var(--bw-highlight-color)",
15960
+ color: "var(--bw-button-text-color, #ffffff)",
15961
+ fontFamily: "var(--bw-font-family)",
15962
+ fontWeight: 700,
15963
+ cursor: canContinueToPayment ? "pointer" : "not-allowed",
15964
+ opacity: canContinueToPayment ? 1 : 0.6,
15965
+ }, children: t("button.continueToPayment") }))] }));
15966
+ if (!freeformConfig) {
15967
+ return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: jsxRuntime.jsx("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
15968
+ }
15969
+ return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: jsxRuntime.jsx("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), jsxRuntime.jsx("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)" } })] }), jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), jsxRuntime.jsx("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 && (jsxRuntime.jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), jsxRuntime.jsx("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)" } })] }))] }), jsxRuntime.jsxs("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 && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (jsxRuntime.jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (jsxRuntime.jsx("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
15970
+ ? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
15971
+ : t("booking.freeformNotAvailable") }))] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), jsxRuntime.jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxRuntime.jsx("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)" } }), jsxRuntime.jsx("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)" } }), jsxRuntime.jsx("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)" } }), jsxRuntime.jsx("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" } }), jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { children: t("booking.duration") }), jsxRuntime.jsx("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [jsxRuntime.jsx("span", { children: t("booking.price") }), jsxRuntime.jsxs("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-color)", fontFamily: "var(--bw-font-family)", fontSize: "20px", fontWeight: 700, marginTop: "10px" }, children: [jsxRuntime.jsx("span", { children: t("summary.totalAmount") }), jsxRuntime.jsx("span", { children: formatCurrency(totalPrice) })] })] })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(HoldCountdown, {}), jsxRuntime.jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsxRuntime.jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (jsxRuntime.jsx("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (jsxRuntime.jsx(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" && (jsxRuntime.jsx(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
15972
+ }
15973
+
15412
15974
  const getThemeConfig = (theme = "generic") => {
15413
15975
  switch (theme) {
15414
15976
  case "christmas":
@@ -16043,12 +16605,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16043
16605
  const [isSuccess, setIsSuccess] = React.useState(false);
16044
16606
  const [successPaymentId, setSuccessPaymentId] = React.useState(null);
16045
16607
  const [systemConfig, setSystemConfig] = React.useState(null);
16608
+ const [waitlistEnabled, setWaitlistEnabled] = React.useState(false);
16046
16609
  // When true, loadEventInstances skips the single-instance auto-select (used by handleShowAllEvents)
16047
16610
  const skipInstanceAutoSelectRef = React.useRef(false);
16048
16611
  // PERFORMANCE OPTIMIZATION: Lazy component loading
16049
16612
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
16050
16613
  const [shouldRenderUpsells, setShouldRenderUpsells] = React.useState(false);
16051
16614
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = React.useState(false);
16615
+ const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = React.useState(false);
16052
16616
  // Google Ads config (received from API, set once from the first API response)
16053
16617
  const [googleAdsConfig, setGoogleAdsConfig] = React.useState(null);
16054
16618
  const extractGoogleAdsConfig = (data) => {
@@ -16062,6 +16626,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16062
16626
  // Voucher purchase state
16063
16627
  const [voucherConfig, setVoucherConfig] = React.useState(null);
16064
16628
  const [voucherEventTypes, setVoucherEventTypes] = React.useState([]);
16629
+ const [voucherCategories, setVoucherCategories] = React.useState([]);
16065
16630
  const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = React.useState(false);
16066
16631
  const [isVoucherFormOpen, setIsVoucherFormOpen] = React.useState(false);
16067
16632
  const [voucherPurchaseResult, setVoucherPurchaseResult] = React.useState(null);
@@ -16141,6 +16706,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16141
16706
  setVoucherConfig(mergedConfig);
16142
16707
  extractGoogleAdsConfig(data);
16143
16708
  setVoucherEventTypes(data.eventTypes || []);
16709
+ setVoucherCategories(data.categories || []);
16144
16710
  // Set system config for payment processing
16145
16711
  if (data.paymentProvider) {
16146
16712
  setSystemConfig({
@@ -16169,7 +16735,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16169
16735
  if (!analyticsInitRef.current && config.organizationId) {
16170
16736
  analyticsInitRef.current = true;
16171
16737
  initAnalytics(config.apiBaseUrl, config.organizationId, {
16172
- partnerContractId: config.partnerContractId,
16738
+ ...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
16173
16739
  });
16174
16740
  trackEvent("widget_loaded", {
16175
16741
  viewMode,
@@ -16533,6 +17099,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16533
17099
  }
16534
17100
  extractGoogleAdsConfig(data);
16535
17101
  setEventInstances(data.eventInstances);
17102
+ setWaitlistEnabled(data.waitlistEnabled ?? false);
16536
17103
  if (data.paymentProvider) {
16537
17104
  setSystemConfig({
16538
17105
  paymentProvider: data.paymentProvider,
@@ -16648,6 +17215,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16648
17215
  const handleEventTypeSelect = async (eventType) => {
16649
17216
  trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
16650
17217
  setSelectedEventType(eventType);
17218
+ if (eventType.bookingMode === "freeform") {
17219
+ setCurrentStep("freeform");
17220
+ setShouldRenderFreeformSelection(true);
17221
+ setIsLoadingEventInstances(true);
17222
+ try {
17223
+ await loadEventInstances(eventType.id);
17224
+ }
17225
+ finally {
17226
+ setIsLoadingEventInstances(false);
17227
+ }
17228
+ return;
17229
+ }
16651
17230
  setCurrentStep("eventInstances");
16652
17231
  setShouldRenderInstanceSelection(true);
16653
17232
  setIsLoadingEventInstances(true);
@@ -16738,6 +17317,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16738
17317
  setSuccessPaymentId(result.paymentIntent.id);
16739
17318
  setSidebarOpen(false);
16740
17319
  setShouldRenderBookingForm(false);
17320
+ setShouldRenderFreeformSelection(false);
16741
17321
  setBookingPersistedState(null);
16742
17322
  config.onSuccess?.(result);
16743
17323
  };
@@ -17053,6 +17633,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17053
17633
  setShouldRenderInstanceSelection(false);
17054
17634
  setShouldRenderUpsells(false);
17055
17635
  setShouldRenderBookingForm(false);
17636
+ setShouldRenderFreeformSelection(false);
17056
17637
  setSelectedUpsells([]);
17057
17638
  setUpsells([]);
17058
17639
  setBookingPersistedState(null);
@@ -17074,6 +17655,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17074
17655
  setShouldRenderInstanceSelection(false);
17075
17656
  setShouldRenderUpsells(false);
17076
17657
  setShouldRenderBookingForm(false);
17658
+ setShouldRenderFreeformSelection(false);
17077
17659
  setSelectedUpsells([]);
17078
17660
  setUpsells([]);
17079
17661
  setBookingPersistedState(null);
@@ -17086,13 +17668,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17086
17668
  }, isOpen: currentStep === "eventInstances", onClose: () => {
17087
17669
  setShowingPreview(true);
17088
17670
  setCurrentStep("eventTypes");
17089
- }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17671
+ }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17090
17672
  setIsSuccess(false);
17091
17673
  setCurrentStep("eventTypes");
17092
17674
  setShowingPreview(true);
17093
17675
  setSuccessPaymentId(null);
17094
17676
  setShouldRenderInstanceSelection(false);
17095
17677
  setShouldRenderBookingForm(false);
17678
+ setShouldRenderFreeformSelection(false);
17096
17679
  const url = new URL(window.location.href);
17097
17680
  url.searchParams.delete("payment_intent");
17098
17681
  url.searchParams.delete("payment_intent_client_secret");
@@ -17136,13 +17719,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17136
17719
  setCurrentStep("booking");
17137
17720
  setShouldRenderBookingForm(true);
17138
17721
  }
17722
+ else if (selectedEventType?.bookingMode === "freeform") {
17723
+ setCurrentStep("freeform");
17724
+ setShouldRenderFreeformSelection(true);
17725
+ }
17139
17726
  else {
17140
17727
  setCurrentStep("eventInstances");
17141
17728
  setSidebarOpen(true);
17142
17729
  setShouldRenderInstanceSelection(true);
17143
17730
  }
17144
17731
  }, children: [isButtonBusy && jsxRuntime.jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
17145
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsxRuntime.jsx(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 && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(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 })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17732
+ (isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
17733
+ ? t("button.bookNow")
17734
+ : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsxRuntime.jsx(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, waitlistEnabled: waitlistEnabled })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(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" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17735
+ setCurrentStep("eventTypes");
17736
+ setSidebarOpen(false);
17737
+ setShouldRenderFreeformSelection(false);
17738
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17146
17739
  setIsSuccess(false);
17147
17740
  setCurrentStep("eventTypes");
17148
17741
  setSidebarOpen(false);
@@ -17150,6 +17743,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17150
17743
  setShouldRenderInstanceSelection(false);
17151
17744
  setShouldRenderUpsells(false);
17152
17745
  setShouldRenderBookingForm(false);
17746
+ setShouldRenderFreeformSelection(false);
17153
17747
  setSelectedUpsells([]);
17154
17748
  setUpsells([]);
17155
17749
  const url = new URL(window.location.href);
@@ -17162,7 +17756,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17162
17756
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
17163
17757
  }
17164
17758
  // Cards mode (default) - show event type selection with optional voucher card
17165
- const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(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 && (jsxRuntime.jsx(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 && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
17759
+ const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick, showImages: config.showEventImages !== false })), isStandaloneVoucherMode && (jsxRuntime.jsx(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 && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
17166
17760
  display: "inline-block",
17167
17761
  width: "32px",
17168
17762
  height: "32px",
@@ -17203,13 +17797,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17203
17797
  };
17204
17798
  };
17205
17799
  const backHandlers = getBackHandlers();
17206
- return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(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 && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(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 })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17800
+ return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsxRuntime.jsx(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, waitlistEnabled: waitlistEnabled })), shouldRenderUpsells && (jsxRuntime.jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(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" && (jsxRuntime.jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17801
+ setCurrentStep("eventTypes");
17802
+ setShouldRenderFreeformSelection(false);
17803
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17207
17804
  setIsSuccess(false);
17208
17805
  setCurrentStep("eventTypes");
17209
17806
  setSuccessPaymentId(null);
17210
17807
  setShouldRenderInstanceSelection(false);
17211
17808
  setShouldRenderUpsells(false);
17212
17809
  setShouldRenderBookingForm(false);
17810
+ setShouldRenderFreeformSelection(false);
17213
17811
  setSelectedUpsells([]);
17214
17812
  setUpsells([]);
17215
17813
  const url = new URL(window.location.href);
@@ -17219,7 +17817,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17219
17817
  url.searchParams.delete("mollie_payment_id");
17220
17818
  url.searchParams.delete("mollie_status");
17221
17819
  window.history.replaceState({}, "", url.toString());
17222
- }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(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: () => {
17820
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(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: () => {
17223
17821
  setIsSuccess(false);
17224
17822
  setVoucherPurchaseResult(null);
17225
17823
  const url = new URL(window.location.href);
@@ -17267,7 +17865,7 @@ function styleInject(css, ref) {
17267
17865
  }
17268
17866
  }
17269
17867
 
17270
- 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}}";
17868
+ 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}}";
17271
17869
  styleInject(css_248z);
17272
17870
 
17273
17871
  // Export init function for vanilla JS usage