@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.esm.js CHANGED
@@ -149,6 +149,10 @@ const de$1 = {
149
149
  "booking.stepUpsells": "Extras",
150
150
  "booking.stepDetails": "Angaben",
151
151
  "booking.stepPayment": "Zahlung",
152
+ "booking.steps": "{{count}} Schritt(e)",
153
+ "booking.freeformUnitsAvailable": "{{count}} Einheiten verfuegbar",
154
+ "booking.freeformNotAvailable": "Das gewaehlte Zeitfenster ist nicht verfuegbar",
155
+ "booking.freeformMissingConfig": "Die Freeform-Konfiguration fuer diesen Event-Typ fehlt.",
152
156
  // Booking summary
153
157
  "summary.title": "Buchungszusammenfassung",
154
158
  "summary.subtotal": "Zwischensumme:",
@@ -204,6 +208,13 @@ const de$1 = {
204
208
  "voucher.monetaryTypeDesc": "Beliebigen Betrag wählen",
205
209
  "voucher.eventType": "Erlebnisgutschein",
206
210
  "voucher.eventTypeDesc": "Für ein bestimmtes Erlebnis",
211
+ "voucher.categoryType": "Kategoriegutschein",
212
+ "voucher.categoryTypeDesc": "Für ein beliebiges Erlebnis einer Kategorie",
213
+ "voucher.categoryVoucher": "Kategoriegutschein",
214
+ "voucher.selectCategory": "Kategorie wählen",
215
+ "voucher.categoryLabel": "Kategorie",
216
+ "voucher.selectCategoryPlaceholder": "Kategorie auswählen...",
217
+ "voucher.categoryAmount": "Gutscheinwert",
207
218
  "voucher.selectAmount": "Betrag wählen",
208
219
  "voucher.customAmount": "Oder eigenen Betrag eingeben",
209
220
  "voucher.amountRange": "Betrag muss zwischen {{min}} und {{max}} liegen",
@@ -457,6 +468,10 @@ const en = {
457
468
  "booking.stepUpsells": "Extras",
458
469
  "booking.stepDetails": "Details",
459
470
  "booking.stepPayment": "Payment",
471
+ "booking.steps": "{{count}} step(s)",
472
+ "booking.freeformUnitsAvailable": "{{count}} units available",
473
+ "booking.freeformNotAvailable": "Selected window is not available",
474
+ "booking.freeformMissingConfig": "Missing freeform configuration for this event type.",
460
475
  // Booking summary
461
476
  "summary.title": "Booking Summary",
462
477
  "summary.subtotal": "Subtotal:",
@@ -512,6 +527,13 @@ const en = {
512
527
  "voucher.monetaryTypeDesc": "Choose any amount",
513
528
  "voucher.eventType": "Event Voucher",
514
529
  "voucher.eventTypeDesc": "For a specific experience",
530
+ "voucher.categoryType": "Category Voucher",
531
+ "voucher.categoryTypeDesc": "For any experience in a category",
532
+ "voucher.categoryVoucher": "Category Voucher",
533
+ "voucher.selectCategory": "Select Category",
534
+ "voucher.categoryLabel": "Category",
535
+ "voucher.selectCategoryPlaceholder": "Choose a category...",
536
+ "voucher.categoryAmount": "Voucher Value",
515
537
  "voucher.selectAmount": "Select Amount",
516
538
  "voucher.customAmount": "Or enter custom amount",
517
539
  "voucher.amountRange": "Amount must be between {{min}} and {{max}}",
@@ -699,6 +721,21 @@ const es = {
699
721
  "instances.bestPrice": "¡¡¡mejor precio!!!",
700
722
  "instances.goodPrice": "buen precio",
701
723
  "instances.oclock": "",
724
+ // Lista de espera
725
+ "waitlist.joinCta": "Lista de espera",
726
+ "waitlist.title": "Apuntarse a la lista de espera",
727
+ "waitlist.subtitle": "{{name}} está agotado. Deja tus datos y te contactaremos si se libera un lugar.",
728
+ "waitlist.name": "Nombre",
729
+ "waitlist.email": "Correo electrónico",
730
+ "waitlist.phone": "Teléfono (opcional)",
731
+ "waitlist.requiredFields": "Por favor, introduce tu nombre y correo electrónico.",
732
+ "waitlist.join": "Apuntarse",
733
+ "waitlist.submitting": "Apuntando...",
734
+ "waitlist.cancel": "Cancelar",
735
+ "waitlist.submitError": "Algo salió mal. Por favor, inténtalo de nuevo.",
736
+ "waitlist.successTitle": "¡Estás en la lista!",
737
+ "waitlist.successMessage": "Eres el número {{position}} en la lista de espera. Te contactaremos si se libera un lugar.",
738
+ "waitlist.done": "Listo",
702
739
  // Next events preview
703
740
  "nextEvents.title": "Próximas fechas disponibles",
704
741
  "nextEvents.subtitle": "Selecciona una fecha o consulta todas las fechas disponibles",
@@ -802,6 +839,13 @@ const es = {
802
839
  "voucher.monetaryTypeDesc": "Elige cualquier cantidad",
803
840
  "voucher.eventType": "Vale de experiencia",
804
841
  "voucher.eventTypeDesc": "Para una experiencia específica",
842
+ "voucher.categoryType": "Vale de categoría",
843
+ "voucher.categoryTypeDesc": "Para cualquier experiencia de una categoría",
844
+ "voucher.categoryVoucher": "Vale de categoría",
845
+ "voucher.selectCategory": "Seleccionar categoría",
846
+ "voucher.categoryLabel": "Categoría",
847
+ "voucher.selectCategoryPlaceholder": "Elige una categoría...",
848
+ "voucher.categoryAmount": "Valor del vale",
805
849
  "voucher.selectAmount": "Selecciona el importe",
806
850
  "voucher.customAmount": "O introduce un importe personalizado",
807
851
  "voucher.amountRange": "El importe debe estar entre {{min}} y {{max}}",
@@ -989,6 +1033,21 @@ const pt = {
989
1033
  "instances.bestPrice": "melhor preço !!!",
990
1034
  "instances.goodPrice": "bom preço",
991
1035
  "instances.oclock": "",
1036
+ // Lista de espera
1037
+ "waitlist.joinCta": "Lista de espera",
1038
+ "waitlist.title": "Entrar na lista de espera",
1039
+ "waitlist.subtitle": "{{name}} está esgotado. Deixe os seus dados e entraremos em contacto se surgir uma vaga.",
1040
+ "waitlist.name": "Nome",
1041
+ "waitlist.email": "E-mail",
1042
+ "waitlist.phone": "Telefone (opcional)",
1043
+ "waitlist.requiredFields": "Por favor, introduza o seu nome e e-mail.",
1044
+ "waitlist.join": "Inscrever-se",
1045
+ "waitlist.submitting": "A inscrever...",
1046
+ "waitlist.cancel": "Cancelar",
1047
+ "waitlist.submitError": "Algo correu mal. Por favor, tente novamente.",
1048
+ "waitlist.successTitle": "Está na lista!",
1049
+ "waitlist.successMessage": "É o número {{position}} na lista de espera. Entraremos em contacto se surgir uma vaga.",
1050
+ "waitlist.done": "Concluído",
992
1051
  // Next events preview
993
1052
  "nextEvents.title": "Próximas datas disponíveis",
994
1053
  "nextEvents.subtitle": "Selecione uma data ou veja todas as datas disponíveis",
@@ -1092,6 +1151,13 @@ const pt = {
1092
1151
  "voucher.monetaryTypeDesc": "Escolha qualquer quantia",
1093
1152
  "voucher.eventType": "Vale de experiência",
1094
1153
  "voucher.eventTypeDesc": "Para uma experiência específica",
1154
+ "voucher.categoryType": "Vale de categoria",
1155
+ "voucher.categoryTypeDesc": "Para qualquer experiência de uma categoria",
1156
+ "voucher.categoryVoucher": "Vale de categoria",
1157
+ "voucher.selectCategory": "Selecionar categoria",
1158
+ "voucher.categoryLabel": "Categoria",
1159
+ "voucher.selectCategoryPlaceholder": "Escolha uma categoria...",
1160
+ "voucher.categoryAmount": "Valor do vale",
1095
1161
  "voucher.selectAmount": "Selecione o valor",
1096
1162
  "voucher.customAmount": "Ou introduza um valor personalizado",
1097
1163
  "voucher.amountRange": "O valor deve estar entre {{min}} e {{max}}",
@@ -1279,6 +1345,21 @@ const sv = {
1279
1345
  "instances.bestPrice": "bästa pris !!!",
1280
1346
  "instances.goodPrice": "bra pris",
1281
1347
  "instances.oclock": "",
1348
+ // Väntelista
1349
+ "waitlist.joinCta": "Ställ dig i kö",
1350
+ "waitlist.title": "Ställ dig i kö",
1351
+ "waitlist.subtitle": "{{name}} är fullbokat. Lämna dina uppgifter så kontaktar vi dig om en plats blir ledig.",
1352
+ "waitlist.name": "Namn",
1353
+ "waitlist.email": "E-post",
1354
+ "waitlist.phone": "Telefon (valfritt)",
1355
+ "waitlist.requiredFields": "Ange ditt namn och din e-postadress.",
1356
+ "waitlist.join": "Ställ dig i kö",
1357
+ "waitlist.submitting": "Anmäler...",
1358
+ "waitlist.cancel": "Avbryt",
1359
+ "waitlist.submitError": "Något gick fel. Försök igen.",
1360
+ "waitlist.successTitle": "Du står i kön!",
1361
+ "waitlist.successMessage": "Du är nummer {{position}} på väntelistan. Vi hör av oss om en plats blir ledig.",
1362
+ "waitlist.done": "Klar",
1282
1363
  // Next events preview
1283
1364
  "nextEvents.title": "Nästa tillgängliga datum",
1284
1365
  "nextEvents.subtitle": "Välj ett datum eller visa alla tillgängliga datum",
@@ -1382,6 +1463,13 @@ const sv = {
1382
1463
  "voucher.monetaryTypeDesc": "Välj valfritt belopp",
1383
1464
  "voucher.eventType": "Upplevelsebevis",
1384
1465
  "voucher.eventTypeDesc": "För en specifik upplevelse",
1466
+ "voucher.categoryType": "Kategoripresent",
1467
+ "voucher.categoryTypeDesc": "För alla upplevelser i en kategori",
1468
+ "voucher.categoryVoucher": "Kategoripresent",
1469
+ "voucher.selectCategory": "Välj kategori",
1470
+ "voucher.categoryLabel": "Kategori",
1471
+ "voucher.selectCategoryPlaceholder": "Välj en kategori...",
1472
+ "voucher.categoryAmount": "Presentkortsvärde",
1385
1473
  "voucher.selectAmount": "Välj belopp",
1386
1474
  "voucher.customAmount": "Eller ange eget belopp",
1387
1475
  "voucher.amountRange": "Beloppet måste vara mellan {{min}} och {{max}}",
@@ -6352,7 +6440,7 @@ function loadMollieScript() {
6352
6440
  document.head.appendChild(script);
6353
6441
  });
6354
6442
  }
6355
- function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, }) {
6443
+ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess: _onSuccess, onError, upsellSelections = [], mollieProfileId, mollieTestmode, freeformSelection, }) {
6356
6444
  const t = useTranslations();
6357
6445
  const { locale } = useLocale();
6358
6446
  const [isLoading, setIsLoading] = useState(false);
@@ -6366,7 +6454,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6366
6454
  const cardFormRef = useRef(null);
6367
6455
  const cardContainerRef = useRef(null);
6368
6456
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6369
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6457
+ const isFullyCoveredByGiftCards = freeformSelection
6458
+ ? false
6459
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
6370
6460
  const isCreditCard = selectedMethod === "creditcard";
6371
6461
  useEffect(() => {
6372
6462
  if (isFullyCoveredByGiftCards)
@@ -6476,6 +6566,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6476
6566
  return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: _onSuccess, onError: onError, upsellSelections: upsellSelections }));
6477
6567
  }
6478
6568
  const validateBeforePayment = () => {
6569
+ if (freeformSelection) {
6570
+ return null;
6571
+ }
6479
6572
  const participantCount = formData.participants.filter((p) => p.name?.trim()).length;
6480
6573
  const availableSpots = eventDetails.availableSpots || 0;
6481
6574
  if (participantCount > availableSpots) {
@@ -6487,21 +6580,41 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6487
6580
  return null;
6488
6581
  };
6489
6582
  const buildRequestData = (cardToken) => ({
6490
- eventInstanceId: config.eventInstanceId || eventDetails.id,
6491
- organizationId: config.organizationId,
6492
- amount: Math.round(totalAmount),
6493
- currency: "EUR",
6494
- participants: formData.participants.filter((p) => p.name?.trim()),
6495
- discountCode: discountCode?.code,
6496
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6497
- customerName: formData.customerName?.trim(),
6498
- customerEmail: formData.customerEmail?.trim(),
6499
- customerPhone: formData.customerPhone?.trim(),
6500
- comment: formData.comment?.trim(),
6501
- ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6502
- ...(cardToken && { cardToken }),
6503
- ...(selectedMethod && !cardToken && { method: selectedMethod }),
6504
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6583
+ ...(freeformSelection
6584
+ ? {
6585
+ eventTypeId: freeformSelection.eventTypeId,
6586
+ organizationId: config.organizationId,
6587
+ start: freeformSelection.start,
6588
+ end: freeformSelection.end,
6589
+ requestedUnits: freeformSelection.requestedUnits,
6590
+ amount: Math.round(totalAmount),
6591
+ currency: "EUR",
6592
+ customerName: freeformSelection.customerName.trim(),
6593
+ customerEmail: freeformSelection.customerEmail.trim(),
6594
+ customerPhone: freeformSelection.customerPhone?.trim(),
6595
+ comment: freeformSelection.comment?.trim(),
6596
+ locale,
6597
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6598
+ ...(cardToken && { cardToken }),
6599
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6600
+ }
6601
+ : {
6602
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
6603
+ organizationId: config.organizationId,
6604
+ amount: Math.round(totalAmount),
6605
+ currency: "EUR",
6606
+ participants: formData.participants.filter((p) => p.name?.trim()),
6607
+ discountCode: discountCode?.code,
6608
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
6609
+ customerName: formData.customerName?.trim(),
6610
+ customerEmail: formData.customerEmail?.trim(),
6611
+ customerPhone: formData.customerPhone?.trim(),
6612
+ comment: formData.comment?.trim(),
6613
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6614
+ ...(cardToken && { cardToken }),
6615
+ ...(selectedMethod && !cardToken && { method: selectedMethod }),
6616
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6617
+ }),
6505
6618
  });
6506
6619
  const handleCardPayment = async () => {
6507
6620
  if (!mollieRef.current)
@@ -6519,7 +6632,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6519
6632
  setPaymentError(tokenError?.message || t("error.createPayment"));
6520
6633
  return;
6521
6634
  }
6522
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6635
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6636
+ ? "/booking/create-freeform-mollie-payment"
6637
+ : "/booking/create-mollie-payment"), {
6523
6638
  method: "POST",
6524
6639
  headers: createApiHeaders(config, locale),
6525
6640
  body: JSON.stringify(createRequestBody(config, buildRequestData(token))),
@@ -6555,7 +6670,9 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6555
6670
  setPaymentError(validationError);
6556
6671
  return;
6557
6672
  }
6558
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-mollie-payment"), {
6673
+ const response = await fetch(getApiUrl(config.apiBaseUrl, freeformSelection
6674
+ ? "/booking/create-freeform-mollie-payment"
6675
+ : "/booking/create-mollie-payment"), {
6559
6676
  method: "POST",
6560
6677
  headers: createApiHeaders(config, locale),
6561
6678
  body: JSON.stringify(createRequestBody(config, buildRequestData())),
@@ -6583,7 +6700,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6583
6700
  ? t("button.processingPayment")
6584
6701
  : selectedMethodData
6585
6702
  ? t("payment.payWithMethod", { method: selectedMethodData.description })
6586
- : totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6703
+ : !freeformSelection && totalAmount < eventDetails.price * formData.participants.filter((p) => p.name?.trim()).length
6587
6704
  ? t("button.depositAndBook")
6588
6705
  : t("button.bookNow");
6589
6706
  const isPayDisabled = isLoading || !selectedMethod || (isCreditCard && !isMollieReady);
@@ -6890,12 +7007,18 @@ var loadStripe = function loadStripe() {
6890
7007
  };
6891
7008
 
6892
7009
  // Inner component that uses the Stripe hooks
6893
- function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
7010
+ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, freeformSelection, }) {
6894
7011
  const t = useTranslations();
6895
- const participantCount = formData.participants.filter((p) => p.name.trim()).length;
6896
- const fullAmount = eventDetails.price * participantCount;
6897
- const submitLabel = totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
7012
+ const isFreeform = Boolean(freeformSelection);
7013
+ const participantCount = isFreeform
7014
+ ? 1
7015
+ : formData.participants.filter((p) => p.name.trim()).length;
7016
+ const fullAmount = isFreeform ? totalAmount : eventDetails.price * participantCount;
7017
+ const submitLabel = !isFreeform && totalAmount < fullAmount ? t("button.depositAndBook") : t("button.bookNow");
6898
7018
  return (jsx(StripeElementsPaymentForm, { onValidate: () => {
7019
+ if (isFreeform) {
7020
+ return null;
7021
+ }
6899
7022
  const availableSpots = eventDetails.availableSpots || 0;
6900
7023
  if (participantCount > availableSpots) {
6901
7024
  return t("payment.tooManyParticipants", { count: participantCount, available: availableSpots });
@@ -6917,7 +7040,7 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
6917
7040
  },
6918
7041
  }, submitContent: jsx(Fragment, { children: submitLabel }), loadingContent: jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
6919
7042
  }
6920
- function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
7043
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], freeformSelection, }) {
6921
7044
  const t = useTranslations();
6922
7045
  const { locale } = useLocale();
6923
7046
  const [clientSecret, setClientSecret] = useState(null);
@@ -6937,7 +7060,9 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6937
7060
  return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
6938
7061
  }, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
6939
7062
  const storageKey = typeof window !== "undefined"
6940
- ? `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
7063
+ ? freeformSelection
7064
+ ? `bw_pi_freeform_${config?.organizationId}_${freeformSelection.eventTypeId}_${freeformSelection.start}_${freeformSelection.end}_${freeformSelection.requestedUnits}`
7065
+ : `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
6941
7066
  : "";
6942
7067
  const PAYMENT_INTENT_TTL = 24 * 60 * 60 * 1000;
6943
7068
  function loadPersistedPaymentIntent() {
@@ -6992,48 +7117,82 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6992
7117
  }, [paymentIntentId]);
6993
7118
  useEffect(() => {
6994
7119
  const createStripePayment = async () => {
6995
- if (!systemConfig || !eventDetails || !formData.participants?.length) {
7120
+ if (!systemConfig || !eventDetails) {
7121
+ return;
7122
+ }
7123
+ if (freeformSelection) {
7124
+ if (!freeformSelection.customerEmail?.trim() ||
7125
+ !freeformSelection.customerName?.trim() ||
7126
+ !freeformSelection.start ||
7127
+ !freeformSelection.end) {
7128
+ return;
7129
+ }
7130
+ }
7131
+ else if (!formData.participants?.length) {
6996
7132
  return;
6997
7133
  }
6998
7134
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
6999
- if (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim()) {
7135
+ if (!freeformSelection &&
7136
+ (participantCount === 0 || !formData.customerEmail?.trim() || !formData.customerName?.trim())) {
7000
7137
  return;
7001
7138
  }
7002
7139
  setIsCreatingPaymentIntent(true);
7003
7140
  setPaymentError(null);
7004
7141
  try {
7005
- const requestData = {
7006
- eventInstanceId: config.eventInstanceId || eventDetails.id,
7007
- organizationId: config.organizationId,
7008
- amount: Math.round(totalAmount),
7009
- currency: "eur",
7010
- participants: formData.participants.filter((p) => p.name?.trim()),
7011
- discountCode: discountCode?.code,
7012
- giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7013
- customerName: formData.customerName?.trim(),
7014
- customerEmail: formData.customerEmail?.trim(),
7015
- customerPhone: formData.customerPhone?.trim(),
7016
- comment: formData.comment?.trim(),
7017
- ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7018
- ...(paymentIntentId && { paymentIntentId }),
7019
- ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7020
- };
7021
- if (!requestData.eventInstanceId) {
7022
- throw new Error("Event instance ID is required");
7023
- }
7142
+ const endpoint = freeformSelection
7143
+ ? "/booking/create-freeform-stripe-payment"
7144
+ : "/booking/create-stripe-payment";
7145
+ const requestData = freeformSelection
7146
+ ? {
7147
+ eventTypeId: freeformSelection.eventTypeId,
7148
+ organizationId: config.organizationId,
7149
+ start: freeformSelection.start,
7150
+ end: freeformSelection.end,
7151
+ requestedUnits: freeformSelection.requestedUnits,
7152
+ amount: Math.round(totalAmount),
7153
+ currency: "eur",
7154
+ customerName: freeformSelection.customerName.trim(),
7155
+ customerEmail: freeformSelection.customerEmail.trim(),
7156
+ customerPhone: freeformSelection.customerPhone?.trim(),
7157
+ comment: freeformSelection.comment?.trim(),
7158
+ locale,
7159
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7160
+ ...(paymentIntentId && { paymentIntentId }),
7161
+ }
7162
+ : {
7163
+ eventInstanceId: config.eventInstanceId || eventDetails.id,
7164
+ organizationId: config.organizationId,
7165
+ amount: Math.round(totalAmount),
7166
+ currency: "eur",
7167
+ participants: formData.participants.filter((p) => p.name?.trim()),
7168
+ discountCode: discountCode?.code,
7169
+ giftCardCodes: giftCards?.map((gc) => gc.code) || [],
7170
+ customerName: formData.customerName?.trim(),
7171
+ customerEmail: formData.customerEmail?.trim(),
7172
+ customerPhone: formData.customerPhone?.trim(),
7173
+ comment: formData.comment?.trim(),
7174
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
7175
+ ...(paymentIntentId && { paymentIntentId }),
7176
+ ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
7177
+ };
7024
7178
  if (!requestData.organizationId) {
7025
7179
  throw new Error("Organization ID is required");
7026
7180
  }
7027
- if (!requestData.amount || requestData.amount <= 0) {
7181
+ if (requestData.amount === undefined || requestData.amount < 0) {
7028
7182
  throw new Error("Valid amount is required");
7029
7183
  }
7030
- if (!requestData.participants || requestData.participants.length === 0) {
7184
+ if (!freeformSelection && "eventInstanceId" in requestData && !requestData.eventInstanceId) {
7185
+ throw new Error("Event instance ID is required");
7186
+ }
7187
+ if (!freeformSelection &&
7188
+ "participants" in requestData &&
7189
+ (!requestData.participants || requestData.participants.length === 0)) {
7031
7190
  throw new Error("At least one participant is required");
7032
7191
  }
7033
7192
  if (!requestData.customerEmail) {
7034
7193
  throw new Error("Customer email is required");
7035
7194
  }
7036
- const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-stripe-payment"), {
7195
+ const response = await fetch(getApiUrl(config.apiBaseUrl, endpoint), {
7037
7196
  method: "POST",
7038
7197
  headers: createApiHeaders(config, locale),
7039
7198
  body: JSON.stringify(createRequestBody(config, requestData)),
@@ -7078,9 +7237,13 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7078
7237
  giftCards,
7079
7238
  config,
7080
7239
  upsellSelections,
7240
+ freeformSelection,
7241
+ locale,
7081
7242
  ]);
7082
7243
  const participantCount = formData.participants?.filter((p) => p.name?.trim()).length || 0;
7083
- const isFullyCoveredByGiftCards = isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7244
+ const isFullyCoveredByGiftCards = freeformSelection
7245
+ ? false
7246
+ : isGiftCardFullyCovered(giftCards, eventDetails?.price || 0, participantCount, discountCode?.discountAmount || 0);
7084
7247
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
7085
7248
  return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
7086
7249
  }
@@ -7119,7 +7282,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7119
7282
  setPaymentIntentId(null);
7120
7283
  setClientSecret(null);
7121
7284
  onSuccess(result);
7122
- }, onError: onError }) }));
7285
+ }, onError: onError, ...(freeformSelection ? { freeformSelection } : {}) }) }));
7123
7286
  }
7124
7287
 
7125
7288
  const HOLD_DURATION_SECONDS = 15 * 60;
@@ -13379,20 +13542,22 @@ const sectionHeaderStyles = sectionStyles.header;
13379
13542
  const labelStyles = formStyles.label;
13380
13543
  const inputStyles = formStyles.input;
13381
13544
  const errorTextStyles = formStyles.error;
13382
- function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13545
+ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, categories, isOpen, onClose, onSuccess, onError, systemConfig, preselectedEventTypeId, isLoadingEventTypes = false, }) {
13383
13546
  const t = useTranslations();
13384
13547
  const { locale } = useLocale();
13385
13548
  const paymentProvider = systemConfig?.paymentProvider ?? "stripe";
13386
13549
  // Form state
13387
- // Show voucher type selection if both monetary and event vouchers are allowed
13388
- // The event type selection will handle the case when no event types are available
13389
13550
  const canUseMonetaryVoucherType = voucherConfig.allowMonetaryVouchers !== false;
13390
13551
  const canUseEventVoucherType = voucherConfig.allowEventVouchers;
13391
- const showVoucherTypeSelection = canUseMonetaryVoucherType && canUseEventVoucherType && !isLoadingEventTypes;
13552
+ const canUseCategoryVoucherType = canUseEventVoucherType && categories.length > 0;
13553
+ const availableTypeCount = [canUseMonetaryVoucherType, canUseEventVoucherType, canUseCategoryVoucherType].filter(Boolean).length;
13554
+ const showVoucherTypeSelection = availableTypeCount > 1 && !isLoadingEventTypes;
13392
13555
  const [voucherType, setVoucherType] = useState(canUseMonetaryVoucherType ? "monetary" : "event");
13393
13556
  const [monetaryAmount, setMonetaryAmount] = useState(voucherConfig.monetaryPresets[2] || 10000);
13394
13557
  const [selectedEventTypeId, setSelectedEventTypeId] = useState(null);
13395
13558
  const [eventQuantity, setEventQuantity] = useState(1);
13559
+ const [selectedCategoryId, setSelectedCategoryId] = useState(null);
13560
+ const [categoryAmount, setCategoryAmount] = useState(10000);
13396
13561
  const [recipientName, setRecipientName] = useState("");
13397
13562
  const [message, setMessage] = useState("");
13398
13563
  const [customerName, setCustomerName] = useState("");
@@ -13452,6 +13617,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13452
13617
  if (voucherType === "monetary") {
13453
13618
  return monetaryAmount;
13454
13619
  }
13620
+ else if (voucherType === "category") {
13621
+ return categoryAmount;
13622
+ }
13455
13623
  else {
13456
13624
  const eventType = eventTypes.find((et) => et.id === selectedEventTypeId);
13457
13625
  if (eventType) {
@@ -13459,7 +13627,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13459
13627
  }
13460
13628
  return 0;
13461
13629
  }
13462
- }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes]);
13630
+ }, [voucherType, monetaryAmount, selectedEventTypeId, eventQuantity, eventTypes, categoryAmount]);
13463
13631
  const selectedEventType = useMemo(() => {
13464
13632
  return eventTypes.find((et) => et.id === selectedEventTypeId);
13465
13633
  }, [eventTypes, selectedEventTypeId]);
@@ -13468,6 +13636,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13468
13636
  voucherType,
13469
13637
  selectedEventTypeId,
13470
13638
  eventQuantity,
13639
+ selectedCategoryId,
13640
+ categoryAmount,
13471
13641
  recipientName: recipientName.trim(),
13472
13642
  message: message.trim(),
13473
13643
  customerName: customerName.trim(),
@@ -13477,6 +13647,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13477
13647
  voucherType,
13478
13648
  selectedEventTypeId,
13479
13649
  eventQuantity,
13650
+ selectedCategoryId,
13651
+ categoryAmount,
13480
13652
  recipientName,
13481
13653
  message,
13482
13654
  customerName,
@@ -13495,24 +13667,37 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13495
13667
  return false;
13496
13668
  if (voucherType === "event" && !selectedEventTypeId)
13497
13669
  return false;
13670
+ if (voucherType === "category" && !selectedCategoryId)
13671
+ return false;
13498
13672
  return true;
13499
- }, [customerName, customerEmail, acceptTerms, totalAmount, voucherConfig, voucherType, selectedEventTypeId]);
13673
+ }, [customerName, customerEmail, acceptTerms, totalAmount, voucherType, selectedEventTypeId, selectedCategoryId]);
13500
13674
  const defaultMonetaryAmount = useMemo(() => voucherConfig.monetaryPresets[2] || voucherConfig.monetaryPresets[0] || 1000, [voucherConfig.monetaryPresets]);
13501
13675
  const handleVoucherTypeChange = useCallback((nextType) => {
13502
13676
  setVoucherType(nextType);
13503
13677
  setPaymentError(null);
13504
13678
  if (nextType === "event") {
13505
- // Keep voucher type mutually exclusive: entering event flow resets amount selection state.
13506
13679
  setMonetaryAmount(defaultMonetaryAmount);
13680
+ setSelectedCategoryId(null);
13681
+ setCategoryAmount(10000);
13507
13682
  if (eventTypes.length === 1) {
13508
13683
  setSelectedEventTypeId(eventTypes[0]?.id ?? null);
13509
13684
  }
13510
13685
  return;
13511
13686
  }
13512
- // Keep voucher type mutually exclusive: entering amount flow clears event selection.
13687
+ if (nextType === "category") {
13688
+ setMonetaryAmount(defaultMonetaryAmount);
13689
+ setSelectedEventTypeId(null);
13690
+ setEventQuantity(1);
13691
+ if (categories.length === 1) {
13692
+ setSelectedCategoryId(categories[0]?.id ?? null);
13693
+ }
13694
+ return;
13695
+ }
13513
13696
  setSelectedEventTypeId(null);
13514
13697
  setEventQuantity(1);
13515
- }, [defaultMonetaryAmount, eventTypes]);
13698
+ setSelectedCategoryId(null);
13699
+ setCategoryAmount(10000);
13700
+ }, [defaultMonetaryAmount, eventTypes, categories]);
13516
13701
  useEffect(() => {
13517
13702
  if (!canUseMonetaryVoucherType && voucherType === "monetary") {
13518
13703
  setVoucherType("event");
@@ -13553,9 +13738,12 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13553
13738
  organizationId: config.organizationId,
13554
13739
  amount: totalAmount,
13555
13740
  currency: "eur",
13556
- voucherType,
13557
- monetaryValue: voucherType === "monetary" ? totalAmount : undefined,
13741
+ voucherType: voucherType === "category" ? "monetary" : voucherType,
13742
+ monetaryValue: voucherType === "monetary" ? totalAmount
13743
+ : voucherType === "category" ? categoryAmount
13744
+ : undefined,
13558
13745
  eventTypeId: voucherType === "event" ? selectedEventTypeId : undefined,
13746
+ eventCategoryId: voucherType === "category" ? selectedCategoryId : undefined,
13559
13747
  eventAmount: voucherType === "event" ? eventQuantity : undefined,
13560
13748
  recipientName: recipientName.trim() || undefined,
13561
13749
  message: message.trim() || undefined,
@@ -13746,7 +13934,30 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13746
13934
  fontSize: "13px",
13747
13935
  color: "var(--bw-text-muted)",
13748
13936
  marginTop: "4px",
13749
- }, children: t("voucher.eventTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsx("div", { style: {
13937
+ }, children: t("voucher.eventTypeDesc") })] })), canUseCategoryVoucherType && (jsxs("button", { type: "button", onClick: () => handleVoucherTypeChange("category"), style: {
13938
+ flex: 1,
13939
+ padding: "16px",
13940
+ borderRadius: "var(--bw-border-radius)",
13941
+ border: voucherType === "category"
13942
+ ? "2px solid var(--bw-highlight-color)"
13943
+ : "1px solid var(--bw-border-color)",
13944
+ backgroundColor: voucherType === "category"
13945
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
13946
+ : "var(--bw-surface-color)",
13947
+ cursor: "pointer",
13948
+ fontFamily: "var(--bw-font-family)",
13949
+ transition: "all 0.2s ease",
13950
+ textAlign: "center",
13951
+ }, children: [jsx("div", { style: {
13952
+ fontWeight: 600,
13953
+ color: voucherType === "category"
13954
+ ? "var(--bw-highlight-color)"
13955
+ : "var(--bw-text-color)",
13956
+ }, children: t("voucher.categoryType") }), jsx("div", { style: {
13957
+ fontSize: "13px",
13958
+ color: "var(--bw-text-muted)",
13959
+ marginTop: "4px",
13960
+ }, children: t("voucher.categoryTypeDesc") })] }))] })] })), voucherType === "monetary" && (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectAmount") }), jsx("div", { style: {
13750
13961
  display: "grid",
13751
13962
  gridTemplateColumns: "repeat(3, 1fr)",
13752
13963
  gap: "8px",
@@ -13801,7 +14012,50 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13801
14012
  fontSize: "18px",
13802
14013
  color: "var(--bw-highlight-color)",
13803
14014
  fontFamily: "var(--bw-font-family)",
13804
- }, children: formatCurrency(totalAmount) })] }) }))] })), jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsx("p", { style: {
14015
+ }, children: formatCurrency(totalAmount) })] }) }))] })), voucherType === "category" && (jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.selectCategory") }), jsx("div", { style: { marginTop: "12px" }, children: jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxs("div", { children: [jsx("label", { htmlFor: "category-select", style: labelStyles, children: t("voucher.categoryLabel") }), jsxs("select", { id: "category-select", value: selectedCategoryId || "", onChange: (e) => setSelectedCategoryId(e.target.value ? Number(e.target.value) : null), disabled: categories.length === 1, style: {
14016
+ ...inputStyles,
14017
+ cursor: categories.length === 1 ? "not-allowed" : "pointer",
14018
+ }, children: [categories.length > 1 && (jsx("option", { value: "", children: t("voucher.selectCategoryPlaceholder") })), categories.map((cat) => (jsx("option", { value: cat.id, children: cat.name }, cat.id)))] })] }), selectedCategoryId && (jsxs("div", { children: [jsx("label", { htmlFor: "category-amount", style: labelStyles, children: t("voucher.categoryAmount") }), jsx("div", { style: {
14019
+ display: "grid",
14020
+ gridTemplateColumns: "repeat(3, 1fr)",
14021
+ gap: "8px",
14022
+ marginTop: "4px",
14023
+ }, children: voucherConfig.monetaryPresets.map((preset) => (jsx("button", { type: "button", onClick: () => setCategoryAmount(preset), style: {
14024
+ padding: "10px",
14025
+ borderRadius: "var(--bw-border-radius)",
14026
+ border: categoryAmount === preset
14027
+ ? "2px solid var(--bw-highlight-color)"
14028
+ : "1px solid var(--bw-border-color)",
14029
+ backgroundColor: categoryAmount === preset
14030
+ ? "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)"
14031
+ : "var(--bw-surface-color)",
14032
+ cursor: "pointer",
14033
+ fontFamily: "var(--bw-font-family)",
14034
+ fontWeight: 600,
14035
+ fontSize: "14px",
14036
+ color: categoryAmount === preset
14037
+ ? "var(--bw-highlight-color)"
14038
+ : "var(--bw-text-color)",
14039
+ transition: "all 0.2s ease",
14040
+ }, children: formatCurrency(preset) }, preset))) })] }))] }) }), selectedCategoryId && categoryAmount > 0 && (jsx("div", { style: {
14041
+ marginTop: "16px",
14042
+ padding: "12px",
14043
+ backgroundColor: "rgba(var(--bw-highlight-color-rgb, 0, 177, 170), 0.1)",
14044
+ borderRadius: "var(--bw-border-radius)",
14045
+ border: "1px solid var(--bw-highlight-color)",
14046
+ }, children: jsxs("div", { style: {
14047
+ display: "flex",
14048
+ justifyContent: "space-between",
14049
+ alignItems: "center",
14050
+ }, children: [jsx("span", { style: {
14051
+ color: "var(--bw-text-color)",
14052
+ fontFamily: "var(--bw-font-family)",
14053
+ }, children: categories.find((c) => c.id === selectedCategoryId)?.name }), jsx("span", { style: {
14054
+ fontWeight: 700,
14055
+ fontSize: "18px",
14056
+ color: "var(--bw-highlight-color)",
14057
+ fontFamily: "var(--bw-font-family)",
14058
+ }, children: formatCurrency(categoryAmount) })] }) }))] })), jsxs("div", { style: cardStyles, children: [jsx("h2", { style: sectionHeaderStyles, children: t("voucher.recipientSection") }), jsx("p", { style: {
13805
14059
  fontSize: "13px",
13806
14060
  color: "var(--bw-text-muted)",
13807
14061
  fontFamily: "var(--bw-font-family)",
@@ -13834,7 +14088,9 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13834
14088
  fontFamily: "var(--bw-font-family)",
13835
14089
  }, children: voucherType === "monetary"
13836
14090
  ? t("voucher.monetaryVoucher")
13837
- : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxs("div", { style: {
14091
+ : voucherType === "category"
14092
+ ? categories.find((c) => c.id === selectedCategoryId)?.name || t("voucher.categoryVoucher")
14093
+ : `${eventQuantity}x ${selectedEventType?.name || t("voucher.eventVoucher")}` }), recipientName && (jsxs("div", { style: {
13838
14094
  fontSize: "13px",
13839
14095
  color: "var(--bw-text-muted)",
13840
14096
  fontFamily: "var(--bw-font-family)",
@@ -14110,7 +14366,7 @@ function VoucherAttachment({ onClick }) {
14110
14366
  }, children: t("voucher.buyAsGift") })] }));
14111
14367
  }
14112
14368
 
14113
- function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14369
+ function VoucherIntegration({ config, voucherConfig, eventTypes, categories, systemConfig, isFormOpen, isLoadingConfig, preselectedEventTypeId, voucherPurchaseResult, isSuccess, showStandaloneCard, onCardClick, onFormClose, onSuccess, onError, onSuccessModalClose, }) {
14114
14370
  if (!voucherConfig?.enabled) {
14115
14371
  return null;
14116
14372
  }
@@ -14121,7 +14377,7 @@ function VoucherIntegration({ config, voucherConfig, eventTypes, systemConfig, i
14121
14377
  justifyContent: "center",
14122
14378
  }, children: jsx(VoucherPurchaseCard, { config: voucherConfig, minEventPrice: eventTypes.length > 0
14123
14379
  ? Math.min(...eventTypes.map((et) => et.maxPrice))
14124
- : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), jsx(VoucherPurchaseForm, { config: config, voucherConfig: voucherConfig, eventTypes: eventTypes, isOpen: isFormOpen, onClose: onFormClose, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, preselectedEventTypeId: preselectedEventTypeId, isLoadingEventTypes: isLoadingConfig }), isSuccess && voucherPurchaseResult && (jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14380
+ : undefined, fallbackImages: eventTypes.flatMap((eventType) => eventType.images || []), onClick: onCardClick, standalone: true }) }) })), 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 && (jsx(VoucherSuccessModal, { isOpen: true, onClose: onSuccessModalClose, result: voucherPurchaseResult }))] }));
14125
14381
  }
14126
14382
 
14127
14383
  // Helper function to preprocess markdown for underline support
@@ -14181,7 +14437,7 @@ function InfoBadge({ text }) {
14181
14437
  pointerEvents: "none",
14182
14438
  }, children: text }))] }));
14183
14439
  }
14184
- function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
14440
+ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, showImages = true, }) {
14185
14441
  const t = useTranslations();
14186
14442
  const { locale } = useLocale();
14187
14443
  const timezone = useTimezone();
@@ -14260,6 +14516,18 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14260
14516
  gridAutoRows: "1fr",
14261
14517
  }, children: eventTypes.map((eventType) => {
14262
14518
  const isAvailable = eventType.hasAvailableInstances;
14519
+ const hasImage = showImages && eventType.images && eventType.images.length > 0;
14520
+ const availabilityBadge = (jsx("div", { style: {
14521
+ padding: "4px 8px",
14522
+ borderRadius: "var(--bw-border-radius-small)",
14523
+ fontSize: "12px",
14524
+ fontWeight: 600,
14525
+ color: "#ffffff",
14526
+ fontFamily: "var(--bw-font-family)",
14527
+ backgroundColor: isAvailable
14528
+ ? "var(--bw-success-color)"
14529
+ : "var(--bw-error-color)",
14530
+ }, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }));
14263
14531
  return (jsxs("div", { className: "event-type-card", style: {
14264
14532
  position: "relative",
14265
14533
  backgroundColor: "var(--bw-surface-color)",
@@ -14271,17 +14539,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14271
14539
  fontFamily: "var(--bw-font-family)",
14272
14540
  cursor: isAvailable ? "pointer" : "not-allowed",
14273
14541
  opacity: isAvailable ? 1 : 0.6,
14274
- }, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: jsx("div", { style: {
14275
- padding: "4px 8px",
14276
- borderRadius: "var(--bw-border-radius-small)",
14277
- fontSize: "12px",
14278
- fontWeight: 600,
14279
- color: "#ffffff",
14280
- fontFamily: "var(--bw-font-family)",
14281
- backgroundColor: isAvailable
14282
- ? "var(--bw-success-color)"
14283
- : "var(--bw-error-color)",
14284
- }, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }) }), eventType.images && eventType.images.length > 0 && (jsxs(Fragment, { children: [jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsx("div", { style: {
14542
+ }, onClick: () => isAvailable && onEventTypeSelect(eventType), children: [hasImage && (jsx("div", { style: { position: "absolute", top: "16px", right: "16px", zIndex: 10 }, children: availabilityBadge })), hasImage && (jsxs(Fragment, { children: [jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsx("div", { style: {
14285
14543
  fontSize: "13px",
14286
14544
  color: "var(--bw-surface-color)",
14287
14545
  fontWeight: 600,
@@ -14295,7 +14553,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14295
14553
  flexDirection: "column",
14296
14554
  justifyContent: "space-between",
14297
14555
  height: "540px",
14298
- }, children: [jsxs("div", { children: [jsx("h2", { className: "event-type-title", style: {
14556
+ }, children: [jsxs("div", { children: [!hasImage && (jsx("div", { style: { display: "flex", justifyContent: "flex-end", margin: "0 0 8px 0" }, children: availabilityBadge })), jsx("h2", { className: "event-type-title", style: {
14299
14557
  fontSize: "clamp(1.1rem, 2.5vw, 24px)",
14300
14558
  fontWeight: 700,
14301
14559
  color: "var(--bw-text-color)",
@@ -14567,16 +14825,16 @@ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName
14567
14825
  }
14568
14826
 
14569
14827
  const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
14570
- const availabilityRatio = availableSpots / maxParticipants;
14571
- if (availabilityRatio >= 0.6)
14572
- return null;
14573
- if (availabilityRatio === 0) {
14828
+ if (maxParticipants <= 0 || availableSpots <= 0) {
14574
14829
  return {
14575
14830
  text: t("events.soldOut"),
14576
14831
  backgroundColor: "#7f1d1d",
14577
14832
  textColor: "#fca5a5",
14578
14833
  };
14579
14834
  }
14835
+ const availabilityRatio = availableSpots / maxParticipants;
14836
+ if (availabilityRatio >= 0.6)
14837
+ return null;
14580
14838
  if (availabilityRatio <= 0.3) {
14581
14839
  return {
14582
14840
  text: t("instances.almostSoldOut"),
@@ -14690,7 +14948,7 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
14690
14948
  boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
14691
14949
  }, children: formatCurrency(price) }));
14692
14950
  };
14693
- function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
14951
+ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, waitlistEnabled = false, }) {
14694
14952
  const t = useTranslations();
14695
14953
  const { locale } = useLocale();
14696
14954
  const timezone = useTimezone();
@@ -14875,12 +15133,13 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14875
15133
  ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14876
15134
  : undefined,
14877
15135
  }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14878
- const availableSpots = event.maxParticipants - event.participantCount;
14879
- const isFullyBooked = availableSpots === 0;
15136
+ const availableSpots = event.availableSpots;
14880
15137
  const startDate = new Date(event.startTime);
14881
15138
  const isPastEvent = today.toISOString() >= startDate.toISOString();
14882
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14883
- const canWaitlist = isFullyBooked &&
15139
+ const isUnavailable = availableSpots <= 0 || !event.bookingOpen;
15140
+ const isDisabled = isUnavailable || isPastEvent;
15141
+ const canWaitlist = waitlistEnabled &&
15142
+ isUnavailable &&
14884
15143
  !isPastEvent &&
14885
15144
  !!apiBaseUrl &&
14886
15145
  !!organizationId;
@@ -14917,7 +15176,7 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14917
15176
  color: "var(--bw-highlight-color)",
14918
15177
  opacity: 0.8,
14919
15178
  fontSize: "32px",
14920
- }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
15179
+ }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.effectiveMaxParticipants ?? event.maxParticipants, t: t }), jsxs("div", { style: {
14921
15180
  display: "flex",
14922
15181
  justifyContent: "space-between",
14923
15182
  width: "100%",
@@ -15094,18 +15353,19 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
15094
15353
  margin: "0 auto",
15095
15354
  padding: "12px 16px",
15096
15355
  fontFamily: "var(--bw-font-family)",
15097
- }, children: [jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events.map((event) => {
15098
- const availableSpots = event.maxParticipants - event.participantCount;
15099
- const isFullyBooked = availableSpots === 0;
15356
+ }, children: [jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", marginBottom: "10px" }, children: events
15357
+ .filter((event) => {
15100
15358
  const startDate = new Date(event.startTime);
15101
15359
  const isPastEvent = today.toISOString() >= startDate.toISOString();
15102
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
15103
- const availabilityRatio = availableSpots / event.maxParticipants;
15360
+ return !isPastEvent && event.availableSpots > 0 && event.bookingOpen;
15361
+ })
15362
+ .map((event) => {
15363
+ const availableSpots = event.availableSpots;
15364
+ const startDate = new Date(event.startTime);
15104
15365
  const allocationBadge = (() => {
15366
+ const availabilityRatio = availableSpots / event.maxParticipants;
15105
15367
  if (availabilityRatio >= 0.6)
15106
15368
  return null;
15107
- if (availabilityRatio === 0)
15108
- return { text: t("events.soldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
15109
15369
  if (availabilityRatio <= 0.3)
15110
15370
  return { text: t("instances.almostSoldOut"), backgroundColor: "#7f1d1d", textColor: "#fca5a5" };
15111
15371
  return { text: t("instances.popularDate"), backgroundColor: "#b45309", textColor: "#fbbf24" };
@@ -15119,13 +15379,9 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
15119
15379
  padding: "16px 10px",
15120
15380
  transition: "all 0.2s ease",
15121
15381
  fontFamily: "var(--bw-font-family)",
15122
- opacity: isDisabled ? 0.3 : 1,
15123
- filter: isDisabled ? "grayscale(40%)" : "none",
15124
- cursor: isDisabled ? "not-allowed" : "pointer",
15382
+ cursor: "pointer",
15125
15383
  }, onClick: () => {
15126
- if (!isDisabled) {
15127
- handleEventSelect(event.id);
15128
- }
15384
+ handleEventSelect(event.id);
15129
15385
  }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
15130
15386
  position: "absolute",
15131
15387
  inset: 0,
@@ -15380,7 +15636,7 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
15380
15636
  alignItems: "center",
15381
15637
  backgroundColor: "var(--bw-background-color)",
15382
15638
  }, children: [jsx("span", { style: { fontSize: "12px", color: "var(--bw-text-muted)" }, children: isFullyBooked
15383
- ? t("common.fullyBooked")
15639
+ ? t("events.soldOut")
15384
15640
  : t("specials.spotsLeft").replace("{{count}}", String(special.availableSpots)) }), jsxs("span", { style: {
15385
15641
  fontSize: "13px",
15386
15642
  fontWeight: 600,
@@ -15389,6 +15645,312 @@ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsA
15389
15645
  }) })] }));
15390
15646
  }
15391
15647
 
15648
+ function toMinutesForUnit(unit) {
15649
+ switch (unit.toLowerCase()) {
15650
+ case "minute":
15651
+ case "minutes":
15652
+ return 1;
15653
+ case "hour":
15654
+ case "hours":
15655
+ return 60;
15656
+ case "day":
15657
+ case "days":
15658
+ return 24 * 60;
15659
+ default:
15660
+ return 1;
15661
+ }
15662
+ }
15663
+ // --- Timezone helpers ------------------------------------------------------
15664
+ // The booking is evaluated server-side against the organization's local
15665
+ // operating hours. The customer's browser may be in a different timezone, so we
15666
+ // interpret the datetime-local field as a wall-clock time in the ORG timezone
15667
+ // (not the browser's) and convert to a UTC instant for the API.
15668
+ function getTzParts(date, timeZone) {
15669
+ const dtf = new Intl.DateTimeFormat("en-CA", {
15670
+ timeZone,
15671
+ hour12: false,
15672
+ year: "numeric",
15673
+ month: "2-digit",
15674
+ day: "2-digit",
15675
+ hour: "2-digit",
15676
+ minute: "2-digit",
15677
+ second: "2-digit",
15678
+ });
15679
+ const parts = {};
15680
+ for (const part of dtf.formatToParts(date)) {
15681
+ if (part.type !== "literal")
15682
+ parts[part.type] = part.value;
15683
+ }
15684
+ return parts;
15685
+ }
15686
+ function tzOffsetMs(utcMs, timeZone) {
15687
+ const p = getTzParts(new Date(utcMs), timeZone);
15688
+ const hour = p.hour === "24" ? "00" : p.hour;
15689
+ const asLocalUtc = Date.UTC(Number(p.year), Number(p.month) - 1, Number(p.day), Number(hour), Number(p.minute), Number(p.second));
15690
+ return asLocalUtc - utcMs;
15691
+ }
15692
+ /** Convert an org-tz wall-clock string ("YYYY-MM-DDTHH:mm") to a UTC instant. */
15693
+ function zonedWallTimeToUtc(wallTime, timeZone) {
15694
+ const [datePart, timePart] = wallTime.split("T");
15695
+ if (!datePart || !timePart)
15696
+ return new Date(NaN);
15697
+ const [y, mo, d] = datePart.split("-").map(Number);
15698
+ const [h, mi] = timePart.split(":").map(Number);
15699
+ const naiveUtc = Date.UTC(y, (mo ?? 1) - 1, d, h ?? 0, mi ?? 0);
15700
+ // Single-pass offset correction (good enough outside the ~1h DST fold).
15701
+ const offset = tzOffsetMs(naiveUtc, timeZone);
15702
+ return new Date(naiveUtc - offset);
15703
+ }
15704
+ /** Render a UTC instant as an org-tz datetime-local input value. */
15705
+ function utcToZonedInputValue(date, timeZone) {
15706
+ const p = getTzParts(date, timeZone);
15707
+ const hour = p.hour === "24" ? "00" : p.hour;
15708
+ return `${p.year}-${p.month}-${p.day}T${hour}:${p.minute}`;
15709
+ }
15710
+ /** Format a UTC instant for display in the org timezone. */
15711
+ function formatInTimeZone(date, timeZone, locale) {
15712
+ return new Intl.DateTimeFormat(locale, {
15713
+ timeZone,
15714
+ dateStyle: "medium",
15715
+ timeStyle: "short",
15716
+ }).format(date);
15717
+ }
15718
+ function calculateTieredPrice(durationUnits, config) {
15719
+ const tiers = [...(config.tiers ?? [])]
15720
+ .filter((tier) => tier.unitCount > 0 && tier.price >= 0)
15721
+ .sort((a, b) => b.unitCount - a.unitCount);
15722
+ if (tiers.length === 0) {
15723
+ return 0;
15724
+ }
15725
+ let total = 0;
15726
+ let remaining = durationUnits;
15727
+ for (const tier of tiers) {
15728
+ const count = Math.floor(remaining / tier.unitCount);
15729
+ if (count <= 0)
15730
+ continue;
15731
+ total += count * tier.price;
15732
+ remaining -= count * tier.unitCount;
15733
+ }
15734
+ if (remaining > 0) {
15735
+ if (typeof config.perUnitPrice === "number") {
15736
+ total += remaining * config.perUnitPrice;
15737
+ }
15738
+ else {
15739
+ total += tiers[tiers.length - 1]?.price ?? 0;
15740
+ }
15741
+ }
15742
+ return total;
15743
+ }
15744
+ function FreeformSelection({ config, eventType, systemConfig, isOpen, onClose, onSuccess, onError, }) {
15745
+ const t = useTranslations();
15746
+ const { locale } = useLocale();
15747
+ const freeformConfig = eventType.freeformConfig;
15748
+ const timeZone = eventType.organizationTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
15749
+ const isNamedResource = eventType.resourceKind === "named";
15750
+ const maxUnits = isNamedResource ? 1 : Math.max(1, eventType.resourceQuantity ?? 1);
15751
+ const now = useMemo(() => {
15752
+ const next = new Date();
15753
+ next.setMinutes(next.getMinutes() + 30, 0, 0);
15754
+ return next;
15755
+ }, []);
15756
+ const [startInput, setStartInput] = useState(utcToZonedInputValue(now, timeZone));
15757
+ const [duration, setDuration] = useState(Math.max(1, freeformConfig?.minDuration ?? 1));
15758
+ const [requestedUnits, setRequestedUnits] = useState(1);
15759
+ const [customerName, setCustomerName] = useState("");
15760
+ const [customerEmail, setCustomerEmail] = useState("");
15761
+ const [customerPhone, setCustomerPhone] = useState("");
15762
+ const [comment, setComment] = useState("");
15763
+ const [acceptTerms, setAcceptTerms] = useState(false);
15764
+ const [checkoutStep, setCheckoutStep] = useState("details");
15765
+ const [availability, setAvailability] = useState(null);
15766
+ const [availabilityError, setAvailabilityError] = useState(null);
15767
+ const [isCheckingAvailability, setIsCheckingAvailability] = useState(false);
15768
+ useEffect(() => {
15769
+ if (!isOpen)
15770
+ return;
15771
+ setCheckoutStep("details");
15772
+ }, [isOpen]);
15773
+ useEffect(() => {
15774
+ if (!freeformConfig)
15775
+ return;
15776
+ setDuration(Math.max(1, freeformConfig.minDuration));
15777
+ setRequestedUnits(1);
15778
+ setAvailability(null);
15779
+ setAvailabilityError(null);
15780
+ }, [freeformConfig?.resourceId]);
15781
+ const unitMinutes = toMinutesForUnit(freeformConfig?.unit ?? "hour");
15782
+ const startDate = useMemo(() => zonedWallTimeToUtc(startInput, timeZone), [startInput, timeZone]);
15783
+ const endDate = useMemo(() => new Date(startDate.getTime() + duration * unitMinutes * 60000), [duration, startDate, unitMinutes]);
15784
+ useEffect(() => {
15785
+ if (!isOpen || !freeformConfig)
15786
+ return;
15787
+ if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime()))
15788
+ return;
15789
+ const timer = setTimeout(async () => {
15790
+ setIsCheckingAvailability(true);
15791
+ setAvailabilityError(null);
15792
+ try {
15793
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/freeform-availability"), {
15794
+ method: "POST",
15795
+ headers: createApiHeaders(config, locale),
15796
+ body: JSON.stringify(createRequestBody(config, {
15797
+ eventTypeId: eventType.id,
15798
+ requestedUnits,
15799
+ start: startDate.toISOString(),
15800
+ end: endDate.toISOString(),
15801
+ })),
15802
+ });
15803
+ const data = await response.json();
15804
+ if (!response.ok) {
15805
+ setAvailability(null);
15806
+ setAvailabilityError(data.error || t("error.loadBookingData"));
15807
+ return;
15808
+ }
15809
+ setAvailability({
15810
+ available: Boolean(data.available),
15811
+ freeUnits: Number(data.freeUnits ?? 0),
15812
+ capacityUnits: Number(data.capacityUnits ?? 0),
15813
+ usedUnits: Number(data.usedUnits ?? 0),
15814
+ reservationUsedUnits: Number(data.reservationUsedUnits ?? 0),
15815
+ blockUsedUnits: Number(data.blockUsedUnits ?? 0),
15816
+ durationUnits: Number(data.durationUnits ?? 0),
15817
+ roundedDurationMinutes: Number(data.roundedDurationMinutes ?? 0),
15818
+ });
15819
+ }
15820
+ catch (_error) {
15821
+ setAvailability(null);
15822
+ setAvailabilityError(t("error.loadBookingData"));
15823
+ }
15824
+ finally {
15825
+ setIsCheckingAvailability(false);
15826
+ }
15827
+ }, 350);
15828
+ return () => clearTimeout(timer);
15829
+ }, [
15830
+ config,
15831
+ endDate,
15832
+ eventType.id,
15833
+ freeformConfig,
15834
+ isOpen,
15835
+ locale,
15836
+ requestedUnits,
15837
+ startDate,
15838
+ t,
15839
+ ]);
15840
+ const billedDurationUnits = availability?.durationUnits ?? Math.max(1, Math.ceil(duration / Math.max(1, freeformConfig?.stepDuration ?? 1)));
15841
+ const basePrice = useMemo(() => {
15842
+ if (!freeformConfig)
15843
+ return 0;
15844
+ if (freeformConfig.pricingModel === "perUnit") {
15845
+ return (freeformConfig.perUnitPrice ?? 0) * billedDurationUnits;
15846
+ }
15847
+ return calculateTieredPrice(billedDurationUnits, freeformConfig);
15848
+ }, [billedDurationUnits, freeformConfig]);
15849
+ const totalPrice = Math.max(0, basePrice * requestedUnits);
15850
+ const canContinueToPayment = !!freeformConfig &&
15851
+ customerName.trim().length >= 2 &&
15852
+ customerEmail.trim().length > 3 &&
15853
+ acceptTerms &&
15854
+ !!availability?.available &&
15855
+ !isCheckingAvailability;
15856
+ const freeformSelection = useMemo(() => ({
15857
+ eventTypeId: eventType.id,
15858
+ start: startDate.toISOString(),
15859
+ end: endDate.toISOString(),
15860
+ requestedUnits,
15861
+ customerName: customerName.trim(),
15862
+ customerEmail: customerEmail.trim(),
15863
+ customerPhone: customerPhone.trim(),
15864
+ comment: comment.trim(),
15865
+ }), [
15866
+ comment,
15867
+ customerEmail,
15868
+ customerName,
15869
+ customerPhone,
15870
+ endDate,
15871
+ eventType.id,
15872
+ requestedUnits,
15873
+ startDate,
15874
+ ]);
15875
+ const paymentFormData = useMemo(() => ({
15876
+ customerName: customerName.trim(),
15877
+ customerEmail: customerEmail.trim(),
15878
+ customerPhone: customerPhone.trim(),
15879
+ participants: [{ name: customerName.trim() || customerEmail.trim(), level: undefined }],
15880
+ comment: comment.trim(),
15881
+ }), [comment, customerEmail, customerName, customerPhone]);
15882
+ const eventDetailsForPayment = useMemo(() => ({
15883
+ id: eventType.id,
15884
+ name: eventType.name,
15885
+ startTime: startDate.toISOString(),
15886
+ endTime: endDate.toISOString(),
15887
+ price: basePrice,
15888
+ maxParticipants: availability?.capacityUnits ?? requestedUnits,
15889
+ participantCount: 0,
15890
+ availableSpots: availability?.freeUnits ?? requestedUnits,
15891
+ durationDays: 1,
15892
+ durationPerDay: availability?.roundedDurationMinutes ?? duration * unitMinutes,
15893
+ images: eventType.images ?? [],
15894
+ category: eventType.category,
15895
+ organization: {
15896
+ id: config.organizationId,
15897
+ name: "",
15898
+ },
15899
+ bookingOpen: availability?.available ?? false,
15900
+ }), [
15901
+ availability?.available,
15902
+ availability?.capacityUnits,
15903
+ availability?.freeUnits,
15904
+ availability?.roundedDurationMinutes,
15905
+ basePrice,
15906
+ config.organizationId,
15907
+ duration,
15908
+ endDate,
15909
+ eventType.category,
15910
+ eventType.id,
15911
+ eventType.images,
15912
+ eventType.name,
15913
+ requestedUnits,
15914
+ startDate,
15915
+ unitMinutes,
15916
+ ]);
15917
+ const footer = (jsxs("div", { style: { width: "100%", display: "flex", gap: "12px" }, children: [jsx("button", { type: "button", onClick: () => {
15918
+ if (checkoutStep === "payment") {
15919
+ setCheckoutStep("details");
15920
+ }
15921
+ else {
15922
+ onClose();
15923
+ }
15924
+ }, style: {
15925
+ flex: 1,
15926
+ padding: "12px 16px",
15927
+ border: "1px solid var(--bw-border-color)",
15928
+ borderRadius: "var(--bw-border-radius)",
15929
+ backgroundColor: "var(--bw-surface-color)",
15930
+ color: "var(--bw-text-color)",
15931
+ fontFamily: "var(--bw-font-family)",
15932
+ fontWeight: 600,
15933
+ cursor: "pointer",
15934
+ }, children: checkoutStep === "payment" ? t("button.backToDetails") : t("common.back") }), checkoutStep === "details" && (jsx("button", { type: "button", onClick: () => setCheckoutStep("payment"), disabled: !canContinueToPayment, style: {
15935
+ flex: 1,
15936
+ padding: "12px 16px",
15937
+ border: "none",
15938
+ borderRadius: "var(--bw-border-radius)",
15939
+ backgroundColor: "var(--bw-highlight-color)",
15940
+ color: "var(--bw-button-text-color, #ffffff)",
15941
+ fontFamily: "var(--bw-font-family)",
15942
+ fontWeight: 700,
15943
+ cursor: canContinueToPayment ? "pointer" : "not-allowed",
15944
+ opacity: canContinueToPayment ? 1 : 0.6,
15945
+ }, children: t("button.continueToPayment") }))] }));
15946
+ if (!freeformConfig) {
15947
+ return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, children: jsx("div", { style: { padding: "16px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.freeformMissingConfig") }) }));
15948
+ }
15949
+ return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: eventType.name, footer: footer, children: jsx("div", { style: { padding: "16px", display: "flex", flexDirection: "column", gap: "16px" }, children: checkoutStep === "details" ? (jsxs(Fragment, { children: [jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.eventDetails") }), jsxs("div", { style: { display: "grid", gap: "10px" }, children: [jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.date"), 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)" } })] }), jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("booking.duration"), 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 && (jsxs("label", { style: { display: "grid", gap: "6px", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-muted)" }, children: [t("voucher.quantity"), 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)" } })] }))] }), 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 && (jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("common.loading") })), availabilityError && (jsx("div", { style: { marginTop: "8px", fontSize: "13px", color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: availabilityError })), !availabilityError && availability && (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
15950
+ ? t("booking.freeformUnitsAvailable", { count: availability.freeUnits })
15951
+ : t("booking.freeformNotAvailable") }))] }), jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("booking.contactInfo") }), jsxs("div", { style: { display: "grid", gap: "10px" }, children: [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)" } }), 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)" } }), 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)" } }), 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" } }), jsxs("label", { style: { display: "flex", alignItems: "center", gap: "8px", fontFamily: "var(--bw-font-family)", fontSize: "13px", color: "var(--bw-text-muted)" }, children: [jsx("input", { type: "checkbox", checked: acceptTerms, onChange: (event) => setAcceptTerms(event.target.checked) }), t("booking.acceptTerms"), " ", t("booking.terms")] })] })] }), jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.title") }), jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px" }, children: [jsx("span", { children: t("booking.duration") }), jsx("span", { children: t("booking.steps", { count: billedDurationUnits }) })] }), jsxs("div", { style: { display: "flex", justifyContent: "space-between", color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "14px", marginTop: "6px" }, children: [jsx("span", { children: t("booking.price") }), jsxs("span", { children: [formatCurrency(basePrice), " x ", requestedUnits] })] }), 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: [jsx("span", { children: t("summary.totalAmount") }), jsx("span", { children: formatCurrency(totalPrice) })] })] })] })) : (jsxs(Fragment, { children: [jsx(HoldCountdown, {}), jsxs("div", { style: { border: "1px solid var(--bw-border-color)", borderRadius: "var(--bw-border-radius)", padding: "14px", backgroundColor: "var(--bw-surface-color)" }, children: [jsx("h3", { style: { margin: "0 0 12px 0", fontFamily: "var(--bw-font-family)", color: "var(--bw-text-color)" }, children: t("summary.payment") }), !systemConfig?.paymentProvider && (jsx("p", { style: { margin: 0, color: "var(--bw-error-color)", fontFamily: "var(--bw-font-family)" }, children: t("booking.paymentUnavailable") })), systemConfig?.paymentProvider === "mollie" && (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" && (jsx(StripePaymentForm, { config: config, eventDetails: eventDetailsForPayment, formData: paymentFormData, totalAmount: totalPrice, discountCode: null, giftCards: [], onSuccess: onSuccess, onError: onError, systemConfig: systemConfig, freeformSelection: freeformSelection }))] })] })) }) }));
15952
+ }
15953
+
15392
15954
  const getThemeConfig = (theme = "generic") => {
15393
15955
  switch (theme) {
15394
15956
  case "christmas":
@@ -16023,12 +16585,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16023
16585
  const [isSuccess, setIsSuccess] = useState(false);
16024
16586
  const [successPaymentId, setSuccessPaymentId] = useState(null);
16025
16587
  const [systemConfig, setSystemConfig] = useState(null);
16588
+ const [waitlistEnabled, setWaitlistEnabled] = useState(false);
16026
16589
  // When true, loadEventInstances skips the single-instance auto-select (used by handleShowAllEvents)
16027
16590
  const skipInstanceAutoSelectRef = useRef(false);
16028
16591
  // PERFORMANCE OPTIMIZATION: Lazy component loading
16029
16592
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
16030
16593
  const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
16031
16594
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
16595
+ const [shouldRenderFreeformSelection, setShouldRenderFreeformSelection] = useState(false);
16032
16596
  // Google Ads config (received from API, set once from the first API response)
16033
16597
  const [googleAdsConfig, setGoogleAdsConfig] = useState(null);
16034
16598
  const extractGoogleAdsConfig = (data) => {
@@ -16042,6 +16606,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16042
16606
  // Voucher purchase state
16043
16607
  const [voucherConfig, setVoucherConfig] = useState(null);
16044
16608
  const [voucherEventTypes, setVoucherEventTypes] = useState([]);
16609
+ const [voucherCategories, setVoucherCategories] = useState([]);
16045
16610
  const [isLoadingVoucherConfig, setIsLoadingVoucherConfig] = useState(false);
16046
16611
  const [isVoucherFormOpen, setIsVoucherFormOpen] = useState(false);
16047
16612
  const [voucherPurchaseResult, setVoucherPurchaseResult] = useState(null);
@@ -16121,6 +16686,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16121
16686
  setVoucherConfig(mergedConfig);
16122
16687
  extractGoogleAdsConfig(data);
16123
16688
  setVoucherEventTypes(data.eventTypes || []);
16689
+ setVoucherCategories(data.categories || []);
16124
16690
  // Set system config for payment processing
16125
16691
  if (data.paymentProvider) {
16126
16692
  setSystemConfig({
@@ -16149,7 +16715,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16149
16715
  if (!analyticsInitRef.current && config.organizationId) {
16150
16716
  analyticsInitRef.current = true;
16151
16717
  initAnalytics(config.apiBaseUrl, config.organizationId, {
16152
- partnerContractId: config.partnerContractId,
16718
+ ...(config.partnerContractId ? { partnerContractId: config.partnerContractId } : {}),
16153
16719
  });
16154
16720
  trackEvent("widget_loaded", {
16155
16721
  viewMode,
@@ -16513,6 +17079,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16513
17079
  }
16514
17080
  extractGoogleAdsConfig(data);
16515
17081
  setEventInstances(data.eventInstances);
17082
+ setWaitlistEnabled(data.waitlistEnabled ?? false);
16516
17083
  if (data.paymentProvider) {
16517
17084
  setSystemConfig({
16518
17085
  paymentProvider: data.paymentProvider,
@@ -16628,6 +17195,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16628
17195
  const handleEventTypeSelect = async (eventType) => {
16629
17196
  trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
16630
17197
  setSelectedEventType(eventType);
17198
+ if (eventType.bookingMode === "freeform") {
17199
+ setCurrentStep("freeform");
17200
+ setShouldRenderFreeformSelection(true);
17201
+ setIsLoadingEventInstances(true);
17202
+ try {
17203
+ await loadEventInstances(eventType.id);
17204
+ }
17205
+ finally {
17206
+ setIsLoadingEventInstances(false);
17207
+ }
17208
+ return;
17209
+ }
16631
17210
  setCurrentStep("eventInstances");
16632
17211
  setShouldRenderInstanceSelection(true);
16633
17212
  setIsLoadingEventInstances(true);
@@ -16718,6 +17297,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16718
17297
  setSuccessPaymentId(result.paymentIntent.id);
16719
17298
  setSidebarOpen(false);
16720
17299
  setShouldRenderBookingForm(false);
17300
+ setShouldRenderFreeformSelection(false);
16721
17301
  setBookingPersistedState(null);
16722
17302
  config.onSuccess?.(result);
16723
17303
  };
@@ -17033,6 +17613,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17033
17613
  setShouldRenderInstanceSelection(false);
17034
17614
  setShouldRenderUpsells(false);
17035
17615
  setShouldRenderBookingForm(false);
17616
+ setShouldRenderFreeformSelection(false);
17036
17617
  setSelectedUpsells([]);
17037
17618
  setUpsells([]);
17038
17619
  setBookingPersistedState(null);
@@ -17054,6 +17635,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17054
17635
  setShouldRenderInstanceSelection(false);
17055
17636
  setShouldRenderUpsells(false);
17056
17637
  setShouldRenderBookingForm(false);
17638
+ setShouldRenderFreeformSelection(false);
17057
17639
  setSelectedUpsells([]);
17058
17640
  setUpsells([]);
17059
17641
  setBookingPersistedState(null);
@@ -17066,13 +17648,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17066
17648
  }, isOpen: currentStep === "eventInstances", onClose: () => {
17067
17649
  setShowingPreview(true);
17068
17650
  setCurrentStep("eventTypes");
17069
- }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17651
+ }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17070
17652
  setIsSuccess(false);
17071
17653
  setCurrentStep("eventTypes");
17072
17654
  setShowingPreview(true);
17073
17655
  setSuccessPaymentId(null);
17074
17656
  setShouldRenderInstanceSelection(false);
17075
17657
  setShouldRenderBookingForm(false);
17658
+ setShouldRenderFreeformSelection(false);
17076
17659
  const url = new URL(window.location.href);
17077
17660
  url.searchParams.delete("payment_intent");
17078
17661
  url.searchParams.delete("payment_intent_client_secret");
@@ -17116,13 +17699,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17116
17699
  setCurrentStep("booking");
17117
17700
  setShouldRenderBookingForm(true);
17118
17701
  }
17702
+ else if (selectedEventType?.bookingMode === "freeform") {
17703
+ setCurrentStep("freeform");
17704
+ setShouldRenderFreeformSelection(true);
17705
+ }
17119
17706
  else {
17120
17707
  setCurrentStep("eventInstances");
17121
17708
  setSidebarOpen(true);
17122
17709
  setShouldRenderInstanceSelection(true);
17123
17710
  }
17124
17711
  }, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
17125
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (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 && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17712
+ (isDirectInstanceMode || selectedEventType?.bookingMode === "freeform"
17713
+ ? t("button.bookNow")
17714
+ : t("button.viewDates"))] }), shouldRenderInstanceSelection && (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 && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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" && (jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17715
+ setCurrentStep("eventTypes");
17716
+ setSidebarOpen(false);
17717
+ setShouldRenderFreeformSelection(false);
17718
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17126
17719
  setIsSuccess(false);
17127
17720
  setCurrentStep("eventTypes");
17128
17721
  setSidebarOpen(false);
@@ -17130,6 +17723,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17130
17723
  setShouldRenderInstanceSelection(false);
17131
17724
  setShouldRenderUpsells(false);
17132
17725
  setShouldRenderBookingForm(false);
17726
+ setShouldRenderFreeformSelection(false);
17133
17727
  setSelectedUpsells([]);
17134
17728
  setUpsells([]);
17135
17729
  const url = new URL(window.location.href);
@@ -17142,7 +17736,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17142
17736
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
17143
17737
  }
17144
17738
  // Cards mode (default) - show event type selection with optional voucher card
17145
- const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (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 && (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 && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
17739
+ const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (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 && (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 && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
17146
17740
  display: "inline-block",
17147
17741
  width: "32px",
17148
17742
  height: "32px",
@@ -17183,13 +17777,17 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17183
17777
  };
17184
17778
  };
17185
17779
  const backHandlers = getBackHandlers();
17186
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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 })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17780
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId, waitlistEnabled: waitlistEnabled })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (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" && (jsx(FreeformSelection, { config: config, eventType: selectedEventType, isOpen: currentStep === "freeform", onClose: () => {
17781
+ setCurrentStep("eventTypes");
17782
+ setShouldRenderFreeformSelection(false);
17783
+ }, onSuccess: handleBookingSuccess, onError: handleBookingError, systemConfig: systemConfig })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17187
17784
  setIsSuccess(false);
17188
17785
  setCurrentStep("eventTypes");
17189
17786
  setSuccessPaymentId(null);
17190
17787
  setShouldRenderInstanceSelection(false);
17191
17788
  setShouldRenderUpsells(false);
17192
17789
  setShouldRenderBookingForm(false);
17790
+ setShouldRenderFreeformSelection(false);
17193
17791
  setSelectedUpsells([]);
17194
17792
  setUpsells([]);
17195
17793
  const url = new URL(window.location.href);
@@ -17199,7 +17797,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
17199
17797
  url.searchParams.delete("mollie_payment_id");
17200
17798
  url.searchParams.delete("mollie_status");
17201
17799
  window.history.replaceState({}, "", url.toString());
17202
- }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), 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: () => {
17800
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), 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: () => {
17203
17801
  setIsSuccess(false);
17204
17802
  setVoucherPurchaseResult(null);
17205
17803
  const url = new URL(window.location.href);
@@ -17247,7 +17845,7 @@ function styleInject(css, ref) {
17247
17845
  }
17248
17846
  }
17249
17847
 
17250
- 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}}";
17848
+ 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}}";
17251
17849
  styleInject(css_248z);
17252
17850
 
17253
17851
  // Export init function for vanilla JS usage