@bigz-app/booking-widget 1.3.0 → 1.3.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.
package/dist/index.cjs CHANGED
@@ -300,6 +300,8 @@ const de$1 = {
300
300
  "booking.participantName": "Name *",
301
301
  "booking.participantNamePlaceholder": "Teilnehmername",
302
302
  "booking.participantAge": "Alter",
303
+ "booking.participantLevel": "Level",
304
+ "booking.participantLevelPlaceholder": "Level wählen...",
303
305
  "booking.addParticipant": "{{number}}. Teilnehmer hinzufügen",
304
306
  "booking.maxParticipants": "Maximale Anzahl an Teilnehmern erreicht. Es sind nur noch {{count}} Plätze verfügbar.",
305
307
  "booking.maxSpotsReached": "Maximal {{count}} Plätze verfügbar.",
@@ -448,7 +450,11 @@ const de$1 = {
448
450
  "validation.emailInvalid": "Ungültiges E-Mail-Format",
449
451
  "validation.emailDomainInvalid": "Ungültige E-Mail-Domain",
450
452
  "validation.participantRequired": "Mindestens ein Teilnehmer erforderlich",
453
+ "validation.ageRequired": "Alter ist erforderlich",
454
+ "validation.levelRequired": "Bitte ein Level auswählen",
451
455
  "validation.acceptTerms": "Bitte akzeptiere die Allgemeinen Geschäftsbedingungen",
456
+ "level.beginner": "Anfänger",
457
+ "level.advanced": "Fortgeschritten",
452
458
  // Sidebar
453
459
  "sidebar.close": "Schließen",
454
460
  // Promo
@@ -570,6 +576,8 @@ const en = {
570
576
  "booking.participantName": "Name *",
571
577
  "booking.participantNamePlaceholder": "Participant name",
572
578
  "booking.participantAge": "Age",
579
+ "booking.participantLevel": "Level",
580
+ "booking.participantLevelPlaceholder": "Select level...",
573
581
  "booking.addParticipant": "Add participant {{number}}",
574
582
  "booking.maxParticipants": "Maximum number of participants reached. Only {{count}} spots are available.",
575
583
  "booking.maxSpotsReached": "Maximum {{count}} spots available.",
@@ -718,7 +726,11 @@ const en = {
718
726
  "validation.emailInvalid": "Invalid email format",
719
727
  "validation.emailDomainInvalid": "Invalid email domain",
720
728
  "validation.participantRequired": "At least one participant is required",
729
+ "validation.ageRequired": "Age is required",
730
+ "validation.levelRequired": "Please select a level",
721
731
  "validation.acceptTerms": "Please accept the terms and conditions",
732
+ "level.beginner": "Beginner",
733
+ "level.advanced": "Advanced",
722
734
  // Sidebar
723
735
  "sidebar.close": "Close",
724
736
  // Promo
@@ -840,6 +852,8 @@ const es = {
840
852
  "booking.participantName": "Nombre *",
841
853
  "booking.participantNamePlaceholder": "Nombre del participante",
842
854
  "booking.participantAge": "Edad",
855
+ "booking.participantLevel": "Nivel",
856
+ "booking.participantLevelPlaceholder": "Seleccionar nivel...",
843
857
  "booking.addParticipant": "Añadir participante {{number}}",
844
858
  "booking.maxParticipants": "Número máximo de participantes alcanzado. Solo quedan {{count}} plazas disponibles.",
845
859
  "booking.maxSpotsReached": "Máximo {{count}} plazas disponibles.",
@@ -988,7 +1002,11 @@ const es = {
988
1002
  "validation.emailInvalid": "Formato de correo electrónico inválido",
989
1003
  "validation.emailDomainInvalid": "Dominio de correo electrónico inválido",
990
1004
  "validation.participantRequired": "Se requiere al menos un participante",
1005
+ "validation.ageRequired": "La edad es obligatoria",
1006
+ "validation.levelRequired": "Selecciona un nivel",
991
1007
  "validation.acceptTerms": "Por favor, acepta los términos y condiciones",
1008
+ "level.beginner": "Principiante",
1009
+ "level.advanced": "Avanzado",
992
1010
  // Sidebar
993
1011
  "sidebar.close": "Cerrar",
994
1012
  // Promo
@@ -1110,6 +1128,8 @@ const pt = {
1110
1128
  "booking.participantName": "Nome *",
1111
1129
  "booking.participantNamePlaceholder": "Nome do participante",
1112
1130
  "booking.participantAge": "Idade",
1131
+ "booking.participantLevel": "Nível",
1132
+ "booking.participantLevelPlaceholder": "Selecionar nível...",
1113
1133
  "booking.addParticipant": "Adicionar participante {{number}}",
1114
1134
  "booking.maxParticipants": "Número máximo de participantes atingido. Apenas {{count}} lugares disponíveis.",
1115
1135
  "booking.maxSpotsReached": "Máximo {{count}} lugares disponíveis.",
@@ -1258,7 +1278,11 @@ const pt = {
1258
1278
  "validation.emailInvalid": "Formato de email inválido",
1259
1279
  "validation.emailDomainInvalid": "Domínio de email inválido",
1260
1280
  "validation.participantRequired": "É necessário pelo menos um participante",
1281
+ "validation.ageRequired": "A idade é obrigatória",
1282
+ "validation.levelRequired": "Por favor selecione um nível",
1261
1283
  "validation.acceptTerms": "Por favor, aceite os termos e condições",
1284
+ "level.beginner": "Iniciante",
1285
+ "level.advanced": "Avançado",
1262
1286
  // Sidebar
1263
1287
  "sidebar.close": "Fechar",
1264
1288
  // Promo
@@ -1380,6 +1404,8 @@ const sv = {
1380
1404
  "booking.participantName": "Namn *",
1381
1405
  "booking.participantNamePlaceholder": "Deltagarens namn",
1382
1406
  "booking.participantAge": "Ålder",
1407
+ "booking.participantLevel": "Nivå",
1408
+ "booking.participantLevelPlaceholder": "Välj nivå...",
1383
1409
  "booking.addParticipant": "Lägg till deltagare {{number}}",
1384
1410
  "booking.maxParticipants": "Maximalt antal deltagare uppnått. Bara {{count}} platser är tillgängliga.",
1385
1411
  "booking.maxSpotsReached": "Maximalt {{count}} platser tillgängliga.",
@@ -1528,7 +1554,11 @@ const sv = {
1528
1554
  "validation.emailInvalid": "Ogiltigt e-postformat",
1529
1555
  "validation.emailDomainInvalid": "Ogiltig e-postdomän",
1530
1556
  "validation.participantRequired": "Minst en deltagare krävs",
1557
+ "validation.ageRequired": "Ålder krävs",
1558
+ "validation.levelRequired": "Välj en nivå",
1531
1559
  "validation.acceptTerms": "Acceptera villkoren",
1560
+ "level.beginner": "Nybörjare",
1561
+ "level.advanced": "Avancerad",
1532
1562
  // Sidebar
1533
1563
  "sidebar.close": "Stäng",
1534
1564
  // Promo
@@ -1621,18 +1651,9 @@ function persistLocale(locale) {
1621
1651
  }
1622
1652
  const I18nContext = React.createContext(null);
1623
1653
  function I18nProvider({ configLocale, children }) {
1624
- // Priority: configLocale (site owner) > persisted user choice > browser language > "de"
1625
- // If configLocale is set, the site owner has locked the language - don't restore user choice.
1626
- const [overrideLocale, setOverrideLocale] = React.useState(() => {
1627
- if (configLocale)
1628
- return null;
1629
- return readPersistedLocale();
1630
- });
1631
- React.useEffect(() => {
1632
- if (configLocale) {
1633
- setOverrideLocale(null);
1634
- }
1635
- }, [configLocale]);
1654
+ // Priority: persisted user choice > configLocale (org default) > browser language > "de"
1655
+ // This keeps org locale as default, but remembers explicit user overrides across reloads.
1656
+ const [overrideLocale, setOverrideLocale] = React.useState(() => readPersistedLocale());
1636
1657
  const locale = overrideLocale ?? resolveLocale(configLocale);
1637
1658
  const handleSetLocale = React.useCallback((next) => {
1638
1659
  persistLocale(next);
@@ -1764,6 +1785,16 @@ const resolveSemanticColor = (colorValue, fallbackValue) => {
1764
1785
  // If semantic resolution fails, use fallback or return the original value
1765
1786
  return fallbackValue || colorValue;
1766
1787
  };
1788
+ // Legacy theme name redirects (old name → new name)
1789
+ const legacyThemeRedirects = {
1790
+ "light-fresh": "teal-minimal",
1791
+ "light-elegant": "blue-business",
1792
+ "light-vibrant": "orange-raw",
1793
+ "light-professional": "blue-business",
1794
+ "dark-night": "navy-night",
1795
+ "dark-modern": "navy-night",
1796
+ "dark-forest": "green-deep",
1797
+ };
1767
1798
  // Predefined themes
1768
1799
  const themes = {
1769
1800
  // --- Light Themes ---
@@ -1910,7 +1941,9 @@ const StyleProvider = ({ config, children, }) => {
1910
1941
  }, []);
1911
1942
  // PERFORMANCE OPTIMIZATION: Memoize style calculations
1912
1943
  const themedStyles = React.useMemo(() => {
1913
- const themeName = config.theme || "teal-minimal";
1944
+ const rawThemeName = config.theme || "teal-minimal";
1945
+ // Redirect legacy theme names to new names
1946
+ const themeName = legacyThemeRedirects[rawThemeName] || rawThemeName;
1914
1947
  const themeDefaults = themes[themeName] || themes["teal-minimal"];
1915
1948
  const getCSSValue = (value, fallback) => {
1916
1949
  if (!value)
@@ -11203,16 +11236,37 @@ const objectType = ZodObject.create;
11203
11236
  ZodUnion.create;
11204
11237
  ZodIntersection.create;
11205
11238
  ZodTuple.create;
11206
- ZodEnum.create;
11239
+ const enumType = ZodEnum.create;
11207
11240
  ZodPromise.create;
11208
11241
  ZodOptional.create;
11209
11242
  ZodNullable.create;
11243
+ const preprocessType = ZodEffects.createWithPreprocess;
11210
11244
 
11211
- const participantSchema = (t) => objectType({
11212
- name: stringType().trim().min(1, t("validation.nameRequired")),
11245
+ const DEFAULT_PARTICIPANT_FIELDS_CONFIG = {
11246
+ name: { enabled: true, required: true },
11247
+ age: { enabled: true, required: false },
11248
+ level: { enabled: false, required: false },
11249
+ };
11250
+ const participantSchema = (t, fieldsConfig) => objectType({
11251
+ name: stringType().trim().optional(),
11213
11252
  age: numberType().min(0).max(120).optional(),
11253
+ level: preprocessType((value) => (value === "" ? undefined : value), enumType(["beginner", "advanced"]).optional()),
11254
+ })
11255
+ .superRefine((value, ctx) => {
11256
+ if (fieldsConfig.name.required && (!value.name || value.name.trim().length < 1)) {
11257
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11258
+ }
11259
+ if (!fieldsConfig.name.enabled && value.name && value.name.trim().length > 0) {
11260
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11261
+ }
11262
+ if (fieldsConfig.age.required && typeof value.age !== "number") {
11263
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.ageRequired"), path: ["age"] });
11264
+ }
11265
+ if (fieldsConfig.level.required && !value.level) {
11266
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.levelRequired"), path: ["level"] });
11267
+ }
11214
11268
  });
11215
- function createBookingFormSchema(t) {
11269
+ function createBookingFormSchema(t, fieldsConfig = DEFAULT_PARTICIPANT_FIELDS_CONFIG) {
11216
11270
  const tr = t ?? ((key) => key);
11217
11271
  return objectType({
11218
11272
  customerName: stringType().trim().min(2, tr("validation.nameMinLength")),
@@ -11222,7 +11276,7 @@ function createBookingFormSchema(t) {
11222
11276
  .email(tr("validation.emailInvalid"))
11223
11277
  .regex(/\.[a-zA-Z]{2,}$/, tr("validation.emailDomainInvalid")),
11224
11278
  customerPhone: stringType().trim().optional(),
11225
- participants: arrayType(participantSchema(tr)).min(1, tr("validation.participantRequired")),
11279
+ participants: arrayType(participantSchema(tr, fieldsConfig)).min(1, tr("validation.participantRequired")),
11226
11280
  discountCode: stringType().trim().optional(),
11227
11281
  comment: stringType().trim().optional(),
11228
11282
  acceptTerms: booleanType().refine((val) => val === true, {
@@ -11365,7 +11419,8 @@ const participantUpsellStyles = {
11365
11419
  gap: "8px",
11366
11420
  marginTop: "10px",
11367
11421
  paddingTop: "10px",
11368
- borderTop: "1px dashed var(--bw-border-color)",
11422
+ paddingBottom: "25px",
11423
+ borderBottom: "1px dashed var(--bw-border-color)",
11369
11424
  },
11370
11425
  label: {
11371
11426
  display: "inline-flex",
@@ -11423,6 +11478,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11423
11478
  const { locale } = useLocale();
11424
11479
  const timezone = useTimezone();
11425
11480
  const roundEnabled = systemConfig?.roundPricesEnabled !== false;
11481
+ const participantFieldsConfig = eventDetails.participantFieldsConfig ?? DEFAULT_PARTICIPANT_FIELDS_CONFIG;
11482
+ const participantLevelOptions = eventDetails.participantLevelOptions ?? ["beginner", "advanced"];
11426
11483
  const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
11427
11484
  const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
11428
11485
  const raw = Math.round((baseAmount * basisPoints) / 10000);
@@ -11435,18 +11492,19 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11435
11492
  // Per-participant upsell selections: participantIndex -> array of upsell package IDs
11436
11493
  const [participantUpsells, setParticipantUpsells] = React.useState({});
11437
11494
  const form = useForm({
11438
- resolver: t(createBookingFormSchema(t$1)),
11495
+ resolver: t(createBookingFormSchema(t$1, participantFieldsConfig)),
11439
11496
  defaultValues: {
11440
11497
  customerName: "",
11441
11498
  customerEmail: "",
11442
11499
  customerPhone: "",
11443
- participants: [{ name: "" }],
11500
+ participants: [{ name: "", level: undefined }],
11444
11501
  discountCode: "",
11445
11502
  comment: "",
11446
11503
  acceptTerms: false,
11447
11504
  },
11448
11505
  });
11449
11506
  const watchedParticipants = form.watch("participants");
11507
+ const participantCount = watchedParticipants.length;
11450
11508
  const watchedCustomerName = form.watch("customerName");
11451
11509
  const watchedCustomerEmail = form.watch("customerEmail");
11452
11510
  const watchedComment = form.watch("comment");
@@ -11488,14 +11546,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11488
11546
  const calculateBaseTotal = React.useCallback(() => {
11489
11547
  if (!eventDetails)
11490
11548
  return 0;
11491
- return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
11492
- }, [eventDetails, watchedParticipants]);
11549
+ return eventDetails.price * participantCount;
11550
+ }, [eventDetails, participantCount]);
11493
11551
  // Calculate upsells total based on per-participant selections
11494
11552
  const calculateUpsellsTotal = React.useCallback(() => {
11495
11553
  let total = 0;
11496
- watchedParticipants.forEach((participant, index) => {
11497
- // Only count upsells for participants with names
11498
- if (participant.name.trim()) {
11554
+ watchedParticipants.forEach((_, index) => {
11555
+ if (participantCount > 0) {
11499
11556
  const participantUpsellIds = participantUpsells[index] || [];
11500
11557
  participantUpsellIds.forEach(upsellId => {
11501
11558
  const upsell = upsells.find(u => u.id === upsellId);
@@ -11506,7 +11563,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11506
11563
  }
11507
11564
  });
11508
11565
  return total;
11509
- }, [participantUpsells, upsells, watchedParticipants]);
11566
+ }, [participantUpsells, upsells, watchedParticipants, participantCount]);
11510
11567
  const calculateTotalDiscount = React.useCallback(() => {
11511
11568
  return appliedVouchers.reduce((total, voucher) => {
11512
11569
  if (voucher.type === "discount") {
@@ -11527,8 +11584,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11527
11584
  const calculateDeposit = () => {
11528
11585
  if (!eventDetails || !eventDetails.deposit)
11529
11586
  return 0;
11530
- const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
11531
- return eventDetails.deposit * participantCount;
11587
+ return eventDetails.deposit * watchedParticipants.length;
11532
11588
  };
11533
11589
  const baseTotal = calculateBaseTotal();
11534
11590
  const upsellsTotal = calculateUpsellsTotal();
@@ -11545,8 +11601,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11545
11601
  // Includes participantIndices to track which participants selected each upsell
11546
11602
  const aggregatedUpsellSelections = React.useCallback(() => {
11547
11603
  const upsellParticipantMap = {};
11548
- watchedParticipants.forEach((participant, index) => {
11549
- if (participant.name.trim()) {
11604
+ watchedParticipants.forEach((_, index) => {
11605
+ if (participantCount > 0) {
11550
11606
  const participantUpsellIds = participantUpsells[index] || [];
11551
11607
  participantUpsellIds.forEach(upsellId => {
11552
11608
  if (!upsellParticipantMap[upsellId]) {
@@ -11580,15 +11636,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11580
11636
  setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
11581
11637
  }, []);
11582
11638
  const isReadyForPayment = () => {
11583
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11639
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11584
11640
  const totalParticipantRows = watchedParticipants.length;
11585
- const allParticipantsHaveNames = participantsWithNames === totalParticipantRows;
11641
+ const allParticipantsHaveNames = participantFieldsConfig.name.required
11642
+ ? participantsWithNames === totalParticipantRows
11643
+ : true;
11586
11644
  const participantsWithinLimit = participantsWithNames <= (eventDetails?.availableSpots || 0);
11587
11645
  const hasValidCustomerName = watchedCustomerName && watchedCustomerName.trim().length >= 2;
11588
11646
  const hasValidCustomerEmail = watchedCustomerEmail && watchedCustomerEmail.trim().length > 0 && !customerEmailError;
11589
11647
  return allParticipantsHaveNames &&
11590
11648
  participantsWithinLimit &&
11591
- participantsWithNames > 0 &&
11649
+ (participantFieldsConfig.name.required ? participantsWithNames > 0 : totalParticipantRows > 0) &&
11592
11650
  hasValidCustomerName &&
11593
11651
  hasValidCustomerEmail &&
11594
11652
  watchedAcceptTerms;
@@ -11596,7 +11654,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11596
11654
  React.useEffect(() => {
11597
11655
  if (appliedVouchers.length > 0) {
11598
11656
  const newBaseTotal = eventDetails?.price
11599
- ? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
11657
+ ? eventDetails.price * watchedParticipants.length
11600
11658
  : 0;
11601
11659
  const currentUpsellsTotal = calculateUpsellsTotal();
11602
11660
  const orderTotal = newBaseTotal + currentUpsellsTotal;
@@ -11641,7 +11699,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11641
11699
  const currentParticipants = form.getValues("participants");
11642
11700
  const availableSpots = eventDetails?.availableSpots || 0;
11643
11701
  if (currentParticipants.length < availableSpots) {
11644
- form.setValue("participants", [...currentParticipants, { name: "" }]);
11702
+ form.setValue("participants", [...currentParticipants, { name: "", level: undefined }]);
11645
11703
  }
11646
11704
  else {
11647
11705
  alert(t$1("booking.maxParticipants", { count: availableSpots }));
@@ -11760,7 +11818,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11760
11818
  justifyContent: "space-between",
11761
11819
  alignItems: "center",
11762
11820
  marginBottom: "16px",
11763
- }, children: jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), jsxRuntime.jsx("input", { id: `participant-name-${index}`, ...form.register(`participants.${index}.name`), type: "text", style: inputStyles$1, placeholder: t$1("booking.participantNamePlaceholder") }), form.formState.errors.participants?.[index]?.name && (jsxRuntime.jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] }), jsxRuntime.jsxs("div", { style: { width: "80px" }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsxRuntime.jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11821
+ }, children: jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [participantFieldsConfig.name.enabled && (jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), jsxRuntime.jsx("input", { id: `participant-name-${index}`, ...form.register(`participants.${index}.name`), type: "text", style: inputStyles$1, placeholder: t$1("booking.participantNamePlaceholder") }), form.formState.errors.participants?.[index]?.name && (jsxRuntime.jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] })), participantFieldsConfig.age.enabled && (jsxRuntime.jsxs("div", { style: { width: "80px" }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsxRuntime.jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11764
11822
  setValueAs: (value) => {
11765
11823
  if (value === "" || value === null || value === undefined) {
11766
11824
  return undefined;
@@ -11768,7 +11826,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11768
11826
  const num = Number(value);
11769
11827
  return Number.isNaN(num) ? undefined : num;
11770
11828
  },
11771
- }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] }), watchedParticipants.length > 1 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
11829
+ }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] })), watchedParticipants.length > 1 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
11772
11830
  color: "var(--bw-error-color)",
11773
11831
  backgroundColor: "var(--bw-surface-color)",
11774
11832
  border: "1px solid var(--bw-border-color)",
@@ -11784,7 +11842,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11784
11842
  fontWeight: 700,
11785
11843
  fontFamily: "var(--bw-font-family)",
11786
11844
  padding: 0,
11787
- }, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11845
+ }, children: "\u00D7" })] }))] }), participantFieldsConfig.level.enabled && (jsxRuntime.jsxs("div", { style: { minWidth: "140px" }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-level-${index}`, style: labelStyles$1, children: t$1("booking.participantLevel") }), jsxRuntime.jsxs("select", { id: `participant-level-${index}`, ...form.register(`participants.${index}.level`), style: inputStyles$1, children: [jsxRuntime.jsx("option", { value: "", children: t$1("booking.participantLevelPlaceholder") }), participantLevelOptions.map((level) => (jsxRuntime.jsx("option", { value: level, children: t$1(`level.${level}`) }, level)))] }), form.formState.errors.participants?.[index]?.level && (jsxRuntime.jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.level?.message }))] })), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11788
11846
  const isSelected = (participantUpsells[index] || []).includes(upsell.id);
11789
11847
  return (jsxRuntime.jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsxRuntime.jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxRuntime.jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
11790
11848
  }) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsxRuntime.jsx("div", { style: {
@@ -11813,9 +11871,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11813
11871
  color: "var(--bw-text-color)",
11814
11872
  fontWeight: 500,
11815
11873
  fontFamily: "var(--bw-font-family)",
11816
- }, children: [jsxRuntime.jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.filter((p) => p.name.trim()).length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "13px", display: "block", marginBottom: "4px" }, children: [t$1("common.extras"), ":"] }), upsells.map((upsell) => {
11874
+ }, children: [jsxRuntime.jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [jsxRuntime.jsxs("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "13px", display: "block", marginBottom: "4px" }, children: [t$1("common.extras"), ":"] }), upsells.map((upsell) => {
11817
11875
  // Count how many participants have this upsell selected
11818
- const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
11876
+ const countWithUpsell = watchedParticipants.filter((_, idx) => (participantUpsells[idx] || []).includes(upsell.id)).length;
11819
11877
  if (countWithUpsell === 0)
11820
11878
  return null;
11821
11879
  const upsellLineTotal = upsell.price * countWithUpsell;
@@ -11916,15 +11974,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11916
11974
  }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), jsxRuntime.jsx("div", { ref: paymentSectionRef, children: (stripePromise || systemConfig?.paymentProvider === "mollie") &&
11917
11975
  (() => {
11918
11976
  if (!isReadyForPayment()) {
11919
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11977
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11920
11978
  const totalParticipantRows = watchedParticipants.length;
11921
11979
  const participantsWithoutNames = totalParticipantRows - participantsWithNames;
11922
11980
  const missing = [];
11923
- if (participantsWithNames === 0) {
11924
- missing.push(t$1("payment.needParticipant"));
11925
- }
11926
- else if (participantsWithoutNames > 0) {
11927
- missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
11981
+ if (participantFieldsConfig.name.required) {
11982
+ if (participantsWithNames === 0) {
11983
+ missing.push(t$1("payment.needParticipant"));
11984
+ }
11985
+ else if (participantsWithoutNames > 0) {
11986
+ missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
11987
+ }
11928
11988
  }
11929
11989
  if (participantsWithNames > (eventDetails?.availableSpots || 0)) {
11930
11990
  missing.push(t$1("payment.reduceParticipants", { count: eventDetails?.availableSpots || 0 }));
@@ -11968,121 +12028,145 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11968
12028
  }
11969
12029
 
11970
12030
  /**
11971
- * Google Ads Conversion Tracking Utility
12031
+ * Google Ads Tracking Utility
11972
12032
  *
11973
- * Simplified utility that waits 1500ms, checks/initializes gtag, and sends conversion.
11974
- */
11975
- /**
11976
- * Check if gtag is available in current or parent window
12033
+ * Handles pageview tracking (widget load) and conversion tracking (successful booking).
12034
+ * Supports both direct gtag.js and GTM dataLayer setups.
11977
12035
  */
11978
12036
  function isGtagAvailable() {
11979
- if (typeof window === "undefined") {
12037
+ if (typeof window === "undefined")
11980
12038
  return false;
11981
- }
11982
- // Check current window
11983
- if (typeof window.gtag === "function") {
12039
+ if (typeof window.gtag === "function")
11984
12040
  return true;
11985
- }
11986
- // Check parent window (for iframe/widget scenarios)
11987
12041
  if (window !== window.parent) {
11988
12042
  try {
11989
- if (typeof window.parent?.gtag === "function") {
12043
+ if (typeof window.parent?.gtag === "function")
11990
12044
  return true;
11991
- }
11992
12045
  }
11993
12046
  catch {
11994
- // Cannot access parent window (cross-origin)
12047
+ // Cross-origin
11995
12048
  }
11996
12049
  }
11997
12050
  return false;
11998
12051
  }
11999
- /**
12000
- * Initialize gtag if not already available
12001
- */
12002
12052
  function initializeGtag(tagId) {
12003
- if (typeof window === "undefined") {
12053
+ if (typeof window === "undefined" || isGtagAvailable())
12004
12054
  return;
12005
- }
12006
- // Skip if gtag already exists
12007
- if (isGtagAvailable()) {
12008
- return;
12009
- }
12010
- // Initialize dataLayer and gtag function
12011
12055
  window.dataLayer = window.dataLayer || [];
12012
12056
  window.gtag = (...args) => {
12013
12057
  window.dataLayer.push(args);
12014
12058
  };
12015
- // Set current timestamp
12016
12059
  window.gtag("js", new Date());
12017
- // Load gtag script
12018
12060
  const script = document.createElement("script");
12019
12061
  script.async = true;
12020
12062
  script.src = `https://www.googletagmanager.com/gtag/js?id=${tagId}`;
12021
12063
  document.head.appendChild(script);
12022
- // Configure the tag
12023
12064
  window.gtag("config", tagId, {
12024
12065
  anonymize_ip: true,
12025
12066
  allow_google_signals: false,
12026
12067
  allow_ad_personalization_signals: false,
12027
12068
  });
12028
12069
  }
12029
- /**
12030
- * Send conversion event using available gtag
12031
- */
12032
- function sendConversion(config) {
12033
- if (typeof window === "undefined") {
12034
- return;
12070
+ function getGtag() {
12071
+ if (typeof window === "undefined")
12072
+ return null;
12073
+ if (typeof window.gtag === "function") {
12074
+ return window.gtag;
12035
12075
  }
12036
- let gtag = window.gtag;
12037
- // Try parent window gtag if current window doesn't have it
12038
- if (typeof gtag !== "function" && window !== window.parent) {
12076
+ if (window !== window.parent) {
12039
12077
  try {
12040
- gtag = window.parent?.gtag;
12078
+ const parentGtag = window.parent?.gtag;
12079
+ if (typeof parentGtag === "function")
12080
+ return parentGtag;
12041
12081
  }
12042
12082
  catch {
12043
- // Cannot access parent window (cross-origin)
12083
+ // Cross-origin
12044
12084
  }
12045
12085
  }
12046
- if (typeof gtag !== "function") {
12086
+ return null;
12087
+ }
12088
+ /**
12089
+ * Push an event to the dataLayer for GTM visibility,
12090
+ * then also fire via gtag for direct Google Ads tracking.
12091
+ */
12092
+ function sendEvent(eventName, params) {
12093
+ if (typeof window === "undefined")
12047
12094
  return;
12095
+ // GTM dataLayer push (object format — visible in Tag Assistant)
12096
+ window.dataLayer = window.dataLayer || [];
12097
+ window.dataLayer.push({
12098
+ event: eventName,
12099
+ ...params,
12100
+ });
12101
+ // gtag call (array format — processed by gtag.js for Google Ads)
12102
+ const gtag = getGtag();
12103
+ if (gtag) {
12104
+ gtag("event", eventName, params);
12048
12105
  }
12049
- // Build conversion data
12050
- const conversionData = {
12106
+ }
12107
+ function sendConversion(config) {
12108
+ const params = {
12051
12109
  send_to: `${config.tagId}/${config.conversionId}`,
12052
12110
  };
12053
- // Add optional parameters
12054
12111
  if (config.conversionValue !== undefined) {
12055
- conversionData.value = config.conversionValue;
12112
+ params.value = config.conversionValue;
12056
12113
  }
12057
12114
  if (config.conversionCurrency) {
12058
- conversionData.currency = config.conversionCurrency;
12115
+ params.currency = config.conversionCurrency;
12059
12116
  }
12060
12117
  if (config.transactionId) {
12061
- conversionData.transaction_id = config.transactionId;
12118
+ params.transaction_id = config.transactionId;
12062
12119
  }
12063
- // Send conversion event
12064
- gtag("event", "conversion", conversionData);
12120
+ sendEvent("conversion", params);
12065
12121
  }
12066
12122
  /**
12067
- * Main function to handle Google Ads conversion tracking
12068
- * Waits 1500ms, checks/initializes gtag, then sends conversion
12123
+ * Track widget pageview (fired once on widget mount).
12069
12124
  */
12070
- function handleGoogleAdsConversion(config) {
12071
- // Validate required config
12072
- if (!config.tagId || !config.conversionId) {
12125
+ function handleGoogleAdsPageview(tagId, consent) {
12126
+ if (!tagId || false || typeof window === "undefined")
12073
12127
  return;
12128
+ if (!isGtagAvailable()) {
12129
+ initializeGtag(tagId);
12074
12130
  }
12075
- // Wait 1500ms before proceeding
12131
+ const fire = () => sendEvent("widget_pageview", {
12132
+ send_to: tagId,
12133
+ page_location: window.location.href,
12134
+ page_title: document.title,
12135
+ });
12136
+ if (isGtagAvailable()) {
12137
+ fire();
12138
+ return;
12139
+ }
12140
+ const script = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${tagId}"]`);
12141
+ if (script) {
12142
+ script.addEventListener("load", fire, { once: true });
12143
+ }
12144
+ }
12145
+ /**
12146
+ * Handle Google Ads conversion tracking.
12147
+ * Waits 1500ms for the success page to settle, then fires.
12148
+ */
12149
+ function handleGoogleAdsConversion(config) {
12150
+ if (!config.tagId || !config.conversionId)
12151
+ return;
12076
12152
  setTimeout(() => {
12077
- // Check if gtag is available, initialize if not
12078
- if (!isGtagAvailable()) {
12079
- initializeGtag(config.tagId);
12153
+ if (isGtagAvailable()) {
12154
+ sendConversion(config);
12155
+ return;
12156
+ }
12157
+ initializeGtag(config.tagId);
12158
+ const script = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${config.tagId}"]`);
12159
+ if (script) {
12160
+ script.addEventListener("load", () => sendConversion(config));
12161
+ script.addEventListener("error", () => sendConversion(config));
12162
+ }
12163
+ else {
12164
+ sendConversion(config);
12080
12165
  }
12081
- sendConversion(config);
12082
12166
  }, 1500);
12083
12167
  }
12084
12168
 
12085
- const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, }) => {
12169
+ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, googleAdsConfig: googleAdsConfigProp, }) => {
12086
12170
  const t = useTranslations();
12087
12171
  const { locale } = useLocale();
12088
12172
  const timezone = useTimezone();
@@ -12099,7 +12183,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12099
12183
  try {
12100
12184
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
12101
12185
  method: "POST",
12102
- headers: createApiHeaders(config),
12186
+ headers: createApiHeaders(config, locale),
12103
12187
  body: JSON.stringify(createRequestBody(config, {
12104
12188
  paymentIntentId: targetPaymentIntentId,
12105
12189
  })),
@@ -12131,20 +12215,16 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12131
12215
  });
12132
12216
  setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
12133
12217
  const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
12218
+ const adsConfig = googleAdsConfigProp ?? data.googleAdsConfig;
12134
12219
  if (finalPaymentStatus === "succeeded" &&
12135
- config.googleAds?.tagId &&
12136
- config.googleAds?.conversionId &&
12137
- config.googleAds?.consent !== false) {
12138
- // Prepare conversion tracking data
12139
- const conversionValue = data.order.total / 100;
12140
- const transactionId = data.order.id;
12141
- // Track the conversion
12220
+ adsConfig?.tagId &&
12221
+ adsConfig?.conversionId) {
12142
12222
  handleGoogleAdsConversion({
12143
- tagId: config.googleAds.tagId,
12144
- conversionId: config.googleAds.conversionId,
12145
- conversionValue,
12146
- conversionCurrency: config.googleAds.conversionCurrency || "EUR",
12147
- transactionId,
12223
+ tagId: adsConfig.tagId,
12224
+ conversionId: adsConfig.conversionId,
12225
+ conversionValue: data.order.total / 100,
12226
+ conversionCurrency: adsConfig.conversionCurrency || "EUR",
12227
+ transactionId: data.order.id,
12148
12228
  });
12149
12229
  }
12150
12230
  }
@@ -12333,7 +12413,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12333
12413
  flexDirection: "column",
12334
12414
  gap: "var(--bw-spacing-small)",
12335
12415
  }, children: formData.participants
12336
- .filter((p) => p.name.trim())
12416
+ .filter((p) => p.name?.trim() || p.age || p.level)
12337
12417
  .map((participant, index) => (jsxRuntime.jsx("div", { className: "print-participant", style: {
12338
12418
  display: "flex",
12339
12419
  justifyContent: "space-between",
@@ -12344,11 +12424,15 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12344
12424
  }, children: jsxRuntime.jsxs("div", { className: "print-participant-info", children: [jsxRuntime.jsx("div", { className: "print-participant-name", style: {
12345
12425
  color: "var(--bw-text-color)",
12346
12426
  fontFamily: "var(--bw-font-family)",
12347
- }, children: participant.name }), participant.age && (jsxRuntime.jsx("div", { className: "print-participant-age", style: {
12427
+ }, children: participant.name || `#${index + 1}` }), participant.age && (jsxRuntime.jsx("div", { className: "print-participant-age", style: {
12348
12428
  color: "var(--bw-text-muted)",
12349
12429
  fontSize: "var(--bw-font-size-small)",
12350
12430
  fontFamily: "var(--bw-font-family)",
12351
- }, children: t("success.age", { age: participant.age }) }))] }) }, index))) }) })] })), jsxRuntime.jsxs("div", { className: "print-booking-card", style: {
12431
+ }, children: t("success.age", { age: participant.age }) })), participant.level && (jsxRuntime.jsxs("div", { style: {
12432
+ color: "var(--bw-text-muted)",
12433
+ fontSize: "var(--bw-font-size-small)",
12434
+ fontFamily: "var(--bw-font-family)",
12435
+ }, children: [t("booking.participantLevel"), ": ", t(`level.${participant.level}`)] }))] }) }, index))) }) })] })), jsxRuntime.jsxs("div", { className: "print-booking-card", style: {
12352
12436
  backgroundColor: "var(--bw-surface-color)",
12353
12437
  border: `1px solid var(--bw-border-color)`,
12354
12438
  borderRadius: "var(--bw-border-radius)",
@@ -15405,6 +15489,13 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15405
15489
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
15406
15490
  const [shouldRenderUpsells, setShouldRenderUpsells] = React.useState(false);
15407
15491
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = React.useState(false);
15492
+ // Google Ads config (received from API, set once from the first API response)
15493
+ const [googleAdsConfig, setGoogleAdsConfig] = React.useState(null);
15494
+ const extractGoogleAdsConfig = (data) => {
15495
+ if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
15496
+ setGoogleAdsConfig(data.googleAdsConfig);
15497
+ }
15498
+ };
15408
15499
  // Promo dialog state
15409
15500
  const [showPromoDialog, setShowPromoDialog] = React.useState(false);
15410
15501
  const [widgetContainerRef, setWidgetContainerRef] = React.useState(null);
@@ -15488,6 +15579,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15488
15579
  image: resolvedImage,
15489
15580
  };
15490
15581
  setVoucherConfig(mergedConfig);
15582
+ extractGoogleAdsConfig(data);
15491
15583
  setVoucherEventTypes(data.eventTypes || []);
15492
15584
  // Set system config for payment processing
15493
15585
  if (data.paymentProvider) {
@@ -15510,6 +15602,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15510
15602
  setIsLoadingVoucherConfig(false);
15511
15603
  }
15512
15604
  };
15605
+ // Fire widget pageview once when Google Ads config is received from API
15606
+ const pageviewFiredRef = React.useRef(false);
15607
+ React.useEffect(() => {
15608
+ if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
15609
+ pageviewFiredRef.current = true;
15610
+ handleGoogleAdsPageview(googleAdsConfig.tagId);
15611
+ }
15612
+ }, [googleAdsConfig]);
15513
15613
  // Determine initial step and load data
15514
15614
  React.useEffect(() => {
15515
15615
  const initializeWidget = async () => {
@@ -15723,6 +15823,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15723
15823
  onWidgetLanguage?.(wl);
15724
15824
  onTimezone?.(wl.timezone);
15725
15825
  }
15826
+ extractGoogleAdsConfig(data);
15726
15827
  setEventTypes(data.eventTypes);
15727
15828
  if (isSingleEventTypeMode && data.eventTypes.length === 1) {
15728
15829
  setSelectedEventType(data.eventTypes[0]);
@@ -15759,6 +15860,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15759
15860
  onWidgetLanguage?.(wl);
15760
15861
  onTimezone?.(wl.timezone);
15761
15862
  }
15863
+ extractGoogleAdsConfig(data);
15762
15864
  setUpcomingEvents(data.upcomingEvents || []);
15763
15865
  }
15764
15866
  else {
@@ -15794,6 +15896,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15794
15896
  onWidgetLanguage?.(wl);
15795
15897
  onTimezone?.(wl.timezone);
15796
15898
  }
15899
+ extractGoogleAdsConfig(data);
15797
15900
  setSpecials(data.specials || []);
15798
15901
  }
15799
15902
  else {
@@ -15820,6 +15923,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15820
15923
  onWidgetLanguage?.(wl);
15821
15924
  onTimezone?.(wl.timezone);
15822
15925
  }
15926
+ extractGoogleAdsConfig(data);
15823
15927
  setEventInstances(data.eventInstances);
15824
15928
  if (data.paymentProvider) {
15825
15929
  setSystemConfig({
@@ -15880,6 +15984,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15880
15984
  onWidgetLanguage?.(wl);
15881
15985
  onTimezone?.(wl.timezone);
15882
15986
  }
15987
+ extractGoogleAdsConfig(data);
15883
15988
  setEventDetails(data.eventDetails);
15884
15989
  setSystemConfig({
15885
15990
  paymentProvider: data.paymentProvider,
@@ -15949,6 +16054,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15949
16054
  onWidgetLanguage?.(wl);
15950
16055
  onTimezone?.(wl.timezone);
15951
16056
  }
16057
+ extractGoogleAdsConfig(data);
15952
16058
  return data.upsells || [];
15953
16059
  }
15954
16060
  else {
@@ -16362,7 +16468,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16362
16468
  url.searchParams.delete("mollie_payment_id");
16363
16469
  url.searchParams.delete("mollie_status");
16364
16470
  window.history.replaceState({}, "", url.toString());
16365
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16471
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16366
16472
  }
16367
16473
  if (viewMode === "specials" && showingPreview) {
16368
16474
  return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [jsxRuntime.jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => {
@@ -16387,7 +16493,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16387
16493
  setShouldRenderBookingForm(false);
16388
16494
  setSelectedUpsells([]);
16389
16495
  setUpsells([]);
16390
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16496
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16391
16497
  }
16392
16498
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
16393
16499
  return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
@@ -16410,7 +16516,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16410
16516
  url.searchParams.delete("mollie_payment_id");
16411
16517
  url.searchParams.delete("mollie_status");
16412
16518
  window.history.replaceState({}, "", url.toString());
16413
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16519
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16414
16520
  }
16415
16521
  if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
16416
16522
  return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, style: {
@@ -16456,7 +16562,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16456
16562
  url.searchParams.delete("mollie_payment_id");
16457
16563
  url.searchParams.delete("mollie_status");
16458
16564
  window.history.replaceState({}, "", url.toString());
16459
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16565
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16460
16566
  }
16461
16567
  // Cards mode (default) - show event type selection with optional voucher card
16462
16568
  const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
@@ -16512,7 +16618,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16512
16618
  url.searchParams.delete("mollie_payment_id");
16513
16619
  url.searchParams.delete("mollie_status");
16514
16620
  window.history.replaceState({}, "", url.toString());
16515
- }, config: config, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
16621
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsxRuntime.jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
16516
16622
  setIsSuccess(false);
16517
16623
  setVoucherPurchaseResult(null);
16518
16624
  const url = new URL(window.location.href);
@@ -16527,11 +16633,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16527
16633
  function UniversalBookingWidget(props) {
16528
16634
  const [languagePolicy, setLanguagePolicy] = React.useState(null);
16529
16635
  const [orgTimezone, setOrgTimezone] = React.useState("Europe/Berlin");
16530
- const serverLockedLocale = languagePolicy && !languagePolicy.multiLanguageEnabled
16531
- ? languagePolicy.organizationLocale
16532
- : undefined;
16533
- const i18nConfigLocale = serverLockedLocale !== undefined ? serverLockedLocale : props.config.locale;
16534
- const providerProps = i18nConfigLocale ? { configLocale: i18nConfigLocale } : {};
16636
+ const providerProps = props.config.locale ? { configLocale: props.config.locale } : {};
16535
16637
  const showLanguagePicker = props.config.showLanguagePicker !== false &&
16536
16638
  (languagePolicy === null || languagePolicy.multiLanguageEnabled);
16537
16639
  return (jsxRuntime.jsx(I18nProvider, { ...providerProps, children: jsxRuntime.jsx(ShowLanguagePickerProvider, { value: showLanguagePicker, children: jsxRuntime.jsx(TimezoneProvider, { value: orgTimezone, children: jsxRuntime.jsx(UniversalBookingWidgetInner, { ...props, onWidgetLanguage: setLanguagePolicy, onTimezone: setOrgTimezone }) }) }) }));