@bigz-app/booking-widget 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -338,6 +338,8 @@
338
338
  "booking.participantName": "Name *",
339
339
  "booking.participantNamePlaceholder": "Teilnehmername",
340
340
  "booking.participantAge": "Alter",
341
+ "booking.participantLevel": "Level",
342
+ "booking.participantLevelPlaceholder": "Level wählen...",
341
343
  "booking.addParticipant": "{{number}}. Teilnehmer hinzufügen",
342
344
  "booking.maxParticipants": "Maximale Anzahl an Teilnehmern erreicht. Es sind nur noch {{count}} Plätze verfügbar.",
343
345
  "booking.maxSpotsReached": "Maximal {{count}} Plätze verfügbar.",
@@ -486,7 +488,11 @@
486
488
  "validation.emailInvalid": "Ungültiges E-Mail-Format",
487
489
  "validation.emailDomainInvalid": "Ungültige E-Mail-Domain",
488
490
  "validation.participantRequired": "Mindestens ein Teilnehmer erforderlich",
491
+ "validation.ageRequired": "Alter ist erforderlich",
492
+ "validation.levelRequired": "Bitte ein Level auswählen",
489
493
  "validation.acceptTerms": "Bitte akzeptiere die Allgemeinen Geschäftsbedingungen",
494
+ "level.beginner": "Anfänger",
495
+ "level.advanced": "Fortgeschritten",
490
496
  // Sidebar
491
497
  "sidebar.close": "Schließen",
492
498
  // Promo
@@ -608,6 +614,8 @@
608
614
  "booking.participantName": "Name *",
609
615
  "booking.participantNamePlaceholder": "Participant name",
610
616
  "booking.participantAge": "Age",
617
+ "booking.participantLevel": "Level",
618
+ "booking.participantLevelPlaceholder": "Select level...",
611
619
  "booking.addParticipant": "Add participant {{number}}",
612
620
  "booking.maxParticipants": "Maximum number of participants reached. Only {{count}} spots are available.",
613
621
  "booking.maxSpotsReached": "Maximum {{count}} spots available.",
@@ -756,7 +764,11 @@
756
764
  "validation.emailInvalid": "Invalid email format",
757
765
  "validation.emailDomainInvalid": "Invalid email domain",
758
766
  "validation.participantRequired": "At least one participant is required",
767
+ "validation.ageRequired": "Age is required",
768
+ "validation.levelRequired": "Please select a level",
759
769
  "validation.acceptTerms": "Please accept the terms and conditions",
770
+ "level.beginner": "Beginner",
771
+ "level.advanced": "Advanced",
760
772
  // Sidebar
761
773
  "sidebar.close": "Close",
762
774
  // Promo
@@ -878,6 +890,8 @@
878
890
  "booking.participantName": "Nombre *",
879
891
  "booking.participantNamePlaceholder": "Nombre del participante",
880
892
  "booking.participantAge": "Edad",
893
+ "booking.participantLevel": "Nivel",
894
+ "booking.participantLevelPlaceholder": "Seleccionar nivel...",
881
895
  "booking.addParticipant": "Añadir participante {{number}}",
882
896
  "booking.maxParticipants": "Número máximo de participantes alcanzado. Solo quedan {{count}} plazas disponibles.",
883
897
  "booking.maxSpotsReached": "Máximo {{count}} plazas disponibles.",
@@ -1026,7 +1040,11 @@
1026
1040
  "validation.emailInvalid": "Formato de correo electrónico inválido",
1027
1041
  "validation.emailDomainInvalid": "Dominio de correo electrónico inválido",
1028
1042
  "validation.participantRequired": "Se requiere al menos un participante",
1043
+ "validation.ageRequired": "La edad es obligatoria",
1044
+ "validation.levelRequired": "Selecciona un nivel",
1029
1045
  "validation.acceptTerms": "Por favor, acepta los términos y condiciones",
1046
+ "level.beginner": "Principiante",
1047
+ "level.advanced": "Avanzado",
1030
1048
  // Sidebar
1031
1049
  "sidebar.close": "Cerrar",
1032
1050
  // Promo
@@ -1148,6 +1166,8 @@
1148
1166
  "booking.participantName": "Nome *",
1149
1167
  "booking.participantNamePlaceholder": "Nome do participante",
1150
1168
  "booking.participantAge": "Idade",
1169
+ "booking.participantLevel": "Nível",
1170
+ "booking.participantLevelPlaceholder": "Selecionar nível...",
1151
1171
  "booking.addParticipant": "Adicionar participante {{number}}",
1152
1172
  "booking.maxParticipants": "Número máximo de participantes atingido. Apenas {{count}} lugares disponíveis.",
1153
1173
  "booking.maxSpotsReached": "Máximo {{count}} lugares disponíveis.",
@@ -1296,7 +1316,11 @@
1296
1316
  "validation.emailInvalid": "Formato de email inválido",
1297
1317
  "validation.emailDomainInvalid": "Domínio de email inválido",
1298
1318
  "validation.participantRequired": "É necessário pelo menos um participante",
1319
+ "validation.ageRequired": "A idade é obrigatória",
1320
+ "validation.levelRequired": "Por favor selecione um nível",
1299
1321
  "validation.acceptTerms": "Por favor, aceite os termos e condições",
1322
+ "level.beginner": "Iniciante",
1323
+ "level.advanced": "Avançado",
1300
1324
  // Sidebar
1301
1325
  "sidebar.close": "Fechar",
1302
1326
  // Promo
@@ -1418,6 +1442,8 @@
1418
1442
  "booking.participantName": "Namn *",
1419
1443
  "booking.participantNamePlaceholder": "Deltagarens namn",
1420
1444
  "booking.participantAge": "Ålder",
1445
+ "booking.participantLevel": "Nivå",
1446
+ "booking.participantLevelPlaceholder": "Välj nivå...",
1421
1447
  "booking.addParticipant": "Lägg till deltagare {{number}}",
1422
1448
  "booking.maxParticipants": "Maximalt antal deltagare uppnått. Bara {{count}} platser är tillgängliga.",
1423
1449
  "booking.maxSpotsReached": "Maximalt {{count}} platser tillgängliga.",
@@ -1566,7 +1592,11 @@
1566
1592
  "validation.emailInvalid": "Ogiltigt e-postformat",
1567
1593
  "validation.emailDomainInvalid": "Ogiltig e-postdomän",
1568
1594
  "validation.participantRequired": "Minst en deltagare krävs",
1595
+ "validation.ageRequired": "Ålder krävs",
1596
+ "validation.levelRequired": "Välj en nivå",
1569
1597
  "validation.acceptTerms": "Acceptera villkoren",
1598
+ "level.beginner": "Nybörjare",
1599
+ "level.advanced": "Avancerad",
1570
1600
  // Sidebar
1571
1601
  "sidebar.close": "Stäng",
1572
1602
  // Promo
@@ -1659,18 +1689,9 @@
1659
1689
  }
1660
1690
  const I18nContext = R$2(null);
1661
1691
  function I18nProvider({ configLocale, children }) {
1662
- // Priority: configLocale (site owner) > persisted user choice > browser language > "de"
1663
- // If configLocale is set, the site owner has locked the language - don't restore user choice.
1664
- const [overrideLocale, setOverrideLocale] = d$1(() => {
1665
- if (configLocale)
1666
- return null;
1667
- return readPersistedLocale();
1668
- });
1669
- y$1(() => {
1670
- if (configLocale) {
1671
- setOverrideLocale(null);
1672
- }
1673
- }, [configLocale]);
1692
+ // Priority: persisted user choice > configLocale (org default) > browser language > "de"
1693
+ // This keeps org locale as default, but remembers explicit user overrides across reloads.
1694
+ const [overrideLocale, setOverrideLocale] = d$1(() => readPersistedLocale());
1674
1695
  const locale = overrideLocale ?? resolveLocale(configLocale);
1675
1696
  const handleSetLocale = q$2((next) => {
1676
1697
  persistLocale(next);
@@ -1802,6 +1823,16 @@
1802
1823
  // If semantic resolution fails, use fallback or return the original value
1803
1824
  return fallbackValue || colorValue;
1804
1825
  };
1826
+ // Legacy theme name redirects (old name → new name)
1827
+ const legacyThemeRedirects = {
1828
+ "light-fresh": "teal-minimal",
1829
+ "light-elegant": "blue-business",
1830
+ "light-vibrant": "orange-raw",
1831
+ "light-professional": "blue-business",
1832
+ "dark-night": "navy-night",
1833
+ "dark-modern": "navy-night",
1834
+ "dark-forest": "green-deep",
1835
+ };
1805
1836
  // Predefined themes
1806
1837
  const themes = {
1807
1838
  // --- Light Themes ---
@@ -1948,7 +1979,9 @@
1948
1979
  }, []);
1949
1980
  // PERFORMANCE OPTIMIZATION: Memoize style calculations
1950
1981
  const themedStyles = T$2(() => {
1951
- const themeName = config.theme || "teal-minimal";
1982
+ const rawThemeName = config.theme || "teal-minimal";
1983
+ // Redirect legacy theme names to new names
1984
+ const themeName = legacyThemeRedirects[rawThemeName] || rawThemeName;
1952
1985
  const themeDefaults = themes[themeName] || themes["teal-minimal"];
1953
1986
  const getCSSValue = (value, fallback) => {
1954
1987
  if (!value)
@@ -11309,16 +11342,37 @@
11309
11342
  ZodUnion.create;
11310
11343
  ZodIntersection.create;
11311
11344
  ZodTuple.create;
11312
- ZodEnum.create;
11345
+ const enumType = ZodEnum.create;
11313
11346
  ZodPromise.create;
11314
11347
  ZodOptional.create;
11315
11348
  ZodNullable.create;
11349
+ const preprocessType = ZodEffects.createWithPreprocess;
11316
11350
 
11317
- const participantSchema = (t) => objectType({
11318
- name: stringType().trim().min(1, t("validation.nameRequired")),
11351
+ const DEFAULT_PARTICIPANT_FIELDS_CONFIG = {
11352
+ name: { enabled: true, required: true },
11353
+ age: { enabled: true, required: false },
11354
+ level: { enabled: false, required: false },
11355
+ };
11356
+ const participantSchema = (t, fieldsConfig) => objectType({
11357
+ name: stringType().trim().optional(),
11319
11358
  age: numberType().min(0).max(120).optional(),
11359
+ level: preprocessType((value) => (value === "" ? undefined : value), enumType(["beginner", "advanced"]).optional()),
11360
+ })
11361
+ .superRefine((value, ctx) => {
11362
+ if (fieldsConfig.name.required && (!value.name || value.name.trim().length < 1)) {
11363
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11364
+ }
11365
+ if (!fieldsConfig.name.enabled && value.name && value.name.trim().length > 0) {
11366
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11367
+ }
11368
+ if (fieldsConfig.age.required && typeof value.age !== "number") {
11369
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.ageRequired"), path: ["age"] });
11370
+ }
11371
+ if (fieldsConfig.level.required && !value.level) {
11372
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.levelRequired"), path: ["level"] });
11373
+ }
11320
11374
  });
11321
- function createBookingFormSchema(t) {
11375
+ function createBookingFormSchema(t, fieldsConfig = DEFAULT_PARTICIPANT_FIELDS_CONFIG) {
11322
11376
  const tr = t ?? ((key) => key);
11323
11377
  return objectType({
11324
11378
  customerName: stringType().trim().min(2, tr("validation.nameMinLength")),
@@ -11328,7 +11382,7 @@
11328
11382
  .email(tr("validation.emailInvalid"))
11329
11383
  .regex(/\.[a-zA-Z]{2,}$/, tr("validation.emailDomainInvalid")),
11330
11384
  customerPhone: stringType().trim().optional(),
11331
- participants: arrayType(participantSchema(tr)).min(1, tr("validation.participantRequired")),
11385
+ participants: arrayType(participantSchema(tr, fieldsConfig)).min(1, tr("validation.participantRequired")),
11332
11386
  discountCode: stringType().trim().optional(),
11333
11387
  comment: stringType().trim().optional(),
11334
11388
  acceptTerms: booleanType().refine((val) => val === true, {
@@ -11471,7 +11525,8 @@
11471
11525
  gap: "8px",
11472
11526
  marginTop: "10px",
11473
11527
  paddingTop: "10px",
11474
- borderTop: "1px dashed var(--bw-border-color)",
11528
+ paddingBottom: "25px",
11529
+ borderBottom: "1px dashed var(--bw-border-color)",
11475
11530
  },
11476
11531
  label: {
11477
11532
  display: "inline-flex",
@@ -11529,6 +11584,8 @@
11529
11584
  const { locale } = useLocale();
11530
11585
  const timezone = useTimezone();
11531
11586
  const roundEnabled = systemConfig?.roundPricesEnabled !== false;
11587
+ const participantFieldsConfig = eventDetails.participantFieldsConfig ?? DEFAULT_PARTICIPANT_FIELDS_CONFIG;
11588
+ const participantLevelOptions = eventDetails.participantLevelOptions ?? ["beginner", "advanced"];
11532
11589
  const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
11533
11590
  const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
11534
11591
  const raw = Math.round((baseAmount * basisPoints) / 10000);
@@ -11541,18 +11598,19 @@
11541
11598
  // Per-participant upsell selections: participantIndex -> array of upsell package IDs
11542
11599
  const [participantUpsells, setParticipantUpsells] = d$1({});
11543
11600
  const form = useForm({
11544
- resolver: t(createBookingFormSchema(t$1)),
11601
+ resolver: t(createBookingFormSchema(t$1, participantFieldsConfig)),
11545
11602
  defaultValues: {
11546
11603
  customerName: "",
11547
11604
  customerEmail: "",
11548
11605
  customerPhone: "",
11549
- participants: [{ name: "" }],
11606
+ participants: [{ name: "", level: undefined }],
11550
11607
  discountCode: "",
11551
11608
  comment: "",
11552
11609
  acceptTerms: false,
11553
11610
  },
11554
11611
  });
11555
11612
  const watchedParticipants = form.watch("participants");
11613
+ const participantCount = watchedParticipants.length;
11556
11614
  const watchedCustomerName = form.watch("customerName");
11557
11615
  const watchedCustomerEmail = form.watch("customerEmail");
11558
11616
  const watchedComment = form.watch("comment");
@@ -11594,14 +11652,13 @@
11594
11652
  const calculateBaseTotal = q$2(() => {
11595
11653
  if (!eventDetails)
11596
11654
  return 0;
11597
- return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
11598
- }, [eventDetails, watchedParticipants]);
11655
+ return eventDetails.price * participantCount;
11656
+ }, [eventDetails, participantCount]);
11599
11657
  // Calculate upsells total based on per-participant selections
11600
11658
  const calculateUpsellsTotal = q$2(() => {
11601
11659
  let total = 0;
11602
- watchedParticipants.forEach((participant, index) => {
11603
- // Only count upsells for participants with names
11604
- if (participant.name.trim()) {
11660
+ watchedParticipants.forEach((_, index) => {
11661
+ if (participantCount > 0) {
11605
11662
  const participantUpsellIds = participantUpsells[index] || [];
11606
11663
  participantUpsellIds.forEach(upsellId => {
11607
11664
  const upsell = upsells.find(u => u.id === upsellId);
@@ -11612,7 +11669,7 @@
11612
11669
  }
11613
11670
  });
11614
11671
  return total;
11615
- }, [participantUpsells, upsells, watchedParticipants]);
11672
+ }, [participantUpsells, upsells, watchedParticipants, participantCount]);
11616
11673
  const calculateTotalDiscount = q$2(() => {
11617
11674
  return appliedVouchers.reduce((total, voucher) => {
11618
11675
  if (voucher.type === "discount") {
@@ -11633,8 +11690,7 @@
11633
11690
  const calculateDeposit = () => {
11634
11691
  if (!eventDetails || !eventDetails.deposit)
11635
11692
  return 0;
11636
- const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
11637
- return eventDetails.deposit * participantCount;
11693
+ return eventDetails.deposit * watchedParticipants.length;
11638
11694
  };
11639
11695
  const baseTotal = calculateBaseTotal();
11640
11696
  const upsellsTotal = calculateUpsellsTotal();
@@ -11651,8 +11707,8 @@
11651
11707
  // Includes participantIndices to track which participants selected each upsell
11652
11708
  const aggregatedUpsellSelections = q$2(() => {
11653
11709
  const upsellParticipantMap = {};
11654
- watchedParticipants.forEach((participant, index) => {
11655
- if (participant.name.trim()) {
11710
+ watchedParticipants.forEach((_, index) => {
11711
+ if (participantCount > 0) {
11656
11712
  const participantUpsellIds = participantUpsells[index] || [];
11657
11713
  participantUpsellIds.forEach(upsellId => {
11658
11714
  if (!upsellParticipantMap[upsellId]) {
@@ -11686,15 +11742,17 @@
11686
11742
  setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
11687
11743
  }, []);
11688
11744
  const isReadyForPayment = () => {
11689
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11745
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11690
11746
  const totalParticipantRows = watchedParticipants.length;
11691
- const allParticipantsHaveNames = participantsWithNames === totalParticipantRows;
11747
+ const allParticipantsHaveNames = participantFieldsConfig.name.required
11748
+ ? participantsWithNames === totalParticipantRows
11749
+ : true;
11692
11750
  const participantsWithinLimit = participantsWithNames <= (eventDetails?.availableSpots || 0);
11693
11751
  const hasValidCustomerName = watchedCustomerName && watchedCustomerName.trim().length >= 2;
11694
11752
  const hasValidCustomerEmail = watchedCustomerEmail && watchedCustomerEmail.trim().length > 0 && !customerEmailError;
11695
11753
  return allParticipantsHaveNames &&
11696
11754
  participantsWithinLimit &&
11697
- participantsWithNames > 0 &&
11755
+ (participantFieldsConfig.name.required ? participantsWithNames > 0 : totalParticipantRows > 0) &&
11698
11756
  hasValidCustomerName &&
11699
11757
  hasValidCustomerEmail &&
11700
11758
  watchedAcceptTerms;
@@ -11702,7 +11760,7 @@
11702
11760
  y$1(() => {
11703
11761
  if (appliedVouchers.length > 0) {
11704
11762
  const newBaseTotal = eventDetails?.price
11705
- ? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
11763
+ ? eventDetails.price * watchedParticipants.length
11706
11764
  : 0;
11707
11765
  const currentUpsellsTotal = calculateUpsellsTotal();
11708
11766
  const orderTotal = newBaseTotal + currentUpsellsTotal;
@@ -11747,7 +11805,7 @@
11747
11805
  const currentParticipants = form.getValues("participants");
11748
11806
  const availableSpots = eventDetails?.availableSpots || 0;
11749
11807
  if (currentParticipants.length < availableSpots) {
11750
- form.setValue("participants", [...currentParticipants, { name: "" }]);
11808
+ form.setValue("participants", [...currentParticipants, { name: "", level: undefined }]);
11751
11809
  }
11752
11810
  else {
11753
11811
  alert(t$1("booking.maxParticipants", { count: availableSpots }));
@@ -11866,7 +11924,7 @@
11866
11924
  justifyContent: "space-between",
11867
11925
  alignItems: "center",
11868
11926
  marginBottom: "16px",
11869
- }, children: u$2("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), u$2("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (u$2("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [u$2("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [u$2("div", { style: { flex: 1 }, children: [u$2("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), u$2("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 && (u$2("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] }), u$2("div", { style: { width: "80px" }, children: [u$2("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), u$2("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11927
+ }, children: u$2("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), u$2("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (u$2("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [u$2("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [participantFieldsConfig.name.enabled && (u$2("div", { style: { flex: 1 }, children: [u$2("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), u$2("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 && (u$2("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] })), participantFieldsConfig.age.enabled && (u$2("div", { style: { width: "80px" }, children: [u$2("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), u$2("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11870
11928
  setValueAs: (value) => {
11871
11929
  if (value === "" || value === null || value === undefined) {
11872
11930
  return undefined;
@@ -11874,7 +11932,7 @@
11874
11932
  const num = Number(value);
11875
11933
  return Number.isNaN(num) ? undefined : num;
11876
11934
  },
11877
- }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] }), watchedParticipants.length > 1 && (u$2("div", { children: [u$2("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), u$2("button", { type: "button", onClick: () => removeParticipant(index), style: {
11935
+ }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] })), watchedParticipants.length > 1 && (u$2("div", { children: [u$2("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), u$2("button", { type: "button", onClick: () => removeParticipant(index), style: {
11878
11936
  color: "var(--bw-error-color)",
11879
11937
  backgroundColor: "var(--bw-surface-color)",
11880
11938
  border: "1px solid var(--bw-border-color)",
@@ -11890,7 +11948,7 @@
11890
11948
  fontWeight: 700,
11891
11949
  fontFamily: "var(--bw-font-family)",
11892
11950
  padding: 0,
11893
- }, children: "\u00D7" })] }))] }), upsells.length > 0 && (u$2("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11951
+ }, children: "\u00D7" })] }))] }), participantFieldsConfig.level.enabled && (u$2("div", { style: { minWidth: "140px" }, children: [u$2("label", { htmlFor: `participant-level-${index}`, style: labelStyles$1, children: t$1("booking.participantLevel") }), u$2("select", { id: `participant-level-${index}`, ...form.register(`participants.${index}.level`), style: inputStyles$1, children: [u$2("option", { value: "", children: t$1("booking.participantLevelPlaceholder") }), participantLevelOptions.map((level) => (u$2("option", { value: level, children: t$1(`level.${level}`) }, level)))] }), form.formState.errors.participants?.[index]?.level && (u$2("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.level?.message }))] })), upsells.length > 0 && (u$2("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11894
11952
  const isSelected = (participantUpsells[index] || []).includes(upsell.id);
11895
11953
  return (u$2("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [u$2("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), u$2("span", { style: { fontWeight: 500 }, children: upsell.name }), u$2("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
11896
11954
  }) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (u$2("div", { style: {
@@ -11919,9 +11977,9 @@
11919
11977
  color: "var(--bw-text-color)",
11920
11978
  fontWeight: 500,
11921
11979
  fontFamily: "var(--bw-font-family)",
11922
- }, children: [u$2("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.filter((p) => p.name.trim()).length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (u$2("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [u$2("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) => {
11980
+ }, children: [u$2("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (u$2("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [u$2("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) => {
11923
11981
  // Count how many participants have this upsell selected
11924
- const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
11982
+ const countWithUpsell = watchedParticipants.filter((_, idx) => (participantUpsells[idx] || []).includes(upsell.id)).length;
11925
11983
  if (countWithUpsell === 0)
11926
11984
  return null;
11927
11985
  const upsellLineTotal = upsell.price * countWithUpsell;
@@ -12022,15 +12080,17 @@
12022
12080
  }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), u$2("div", { ref: paymentSectionRef, children: (stripePromise || systemConfig?.paymentProvider === "mollie") &&
12023
12081
  (() => {
12024
12082
  if (!isReadyForPayment()) {
12025
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
12083
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
12026
12084
  const totalParticipantRows = watchedParticipants.length;
12027
12085
  const participantsWithoutNames = totalParticipantRows - participantsWithNames;
12028
12086
  const missing = [];
12029
- if (participantsWithNames === 0) {
12030
- missing.push(t$1("payment.needParticipant"));
12031
- }
12032
- else if (participantsWithoutNames > 0) {
12033
- missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
12087
+ if (participantFieldsConfig.name.required) {
12088
+ if (participantsWithNames === 0) {
12089
+ missing.push(t$1("payment.needParticipant"));
12090
+ }
12091
+ else if (participantsWithoutNames > 0) {
12092
+ missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
12093
+ }
12034
12094
  }
12035
12095
  if (participantsWithNames > (eventDetails?.availableSpots || 0)) {
12036
12096
  missing.push(t$1("payment.reduceParticipants", { count: eventDetails?.availableSpots || 0 }));
@@ -12205,7 +12265,7 @@
12205
12265
  try {
12206
12266
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
12207
12267
  method: "POST",
12208
- headers: createApiHeaders(config),
12268
+ headers: createApiHeaders(config, locale),
12209
12269
  body: JSON.stringify(createRequestBody(config, {
12210
12270
  paymentIntentId: targetPaymentIntentId,
12211
12271
  })),
@@ -12439,7 +12499,7 @@
12439
12499
  flexDirection: "column",
12440
12500
  gap: "var(--bw-spacing-small)",
12441
12501
  }, children: formData.participants
12442
- .filter((p) => p.name.trim())
12502
+ .filter((p) => p.name?.trim() || p.age || p.level)
12443
12503
  .map((participant, index) => (u$2("div", { className: "print-participant", style: {
12444
12504
  display: "flex",
12445
12505
  justifyContent: "space-between",
@@ -12450,11 +12510,15 @@
12450
12510
  }, children: u$2("div", { className: "print-participant-info", children: [u$2("div", { className: "print-participant-name", style: {
12451
12511
  color: "var(--bw-text-color)",
12452
12512
  fontFamily: "var(--bw-font-family)",
12453
- }, children: participant.name }), participant.age && (u$2("div", { className: "print-participant-age", style: {
12513
+ }, children: participant.name || `#${index + 1}` }), participant.age && (u$2("div", { className: "print-participant-age", style: {
12454
12514
  color: "var(--bw-text-muted)",
12455
12515
  fontSize: "var(--bw-font-size-small)",
12456
12516
  fontFamily: "var(--bw-font-family)",
12457
- }, children: t("success.age", { age: participant.age }) }))] }) }, index))) }) })] })), u$2("div", { className: "print-booking-card", style: {
12517
+ }, children: t("success.age", { age: participant.age }) })), participant.level && (u$2("div", { style: {
12518
+ color: "var(--bw-text-muted)",
12519
+ fontSize: "var(--bw-font-size-small)",
12520
+ fontFamily: "var(--bw-font-family)",
12521
+ }, children: [t("booking.participantLevel"), ": ", t(`level.${participant.level}`)] }))] }) }, index))) }) })] })), u$2("div", { className: "print-booking-card", style: {
12458
12522
  backgroundColor: "var(--bw-surface-color)",
12459
12523
  border: `1px solid var(--bw-border-color)`,
12460
12524
  borderRadius: "var(--bw-border-radius)",
@@ -16633,11 +16697,7 @@
16633
16697
  function UniversalBookingWidget(props) {
16634
16698
  const [languagePolicy, setLanguagePolicy] = d$1(null);
16635
16699
  const [orgTimezone, setOrgTimezone] = d$1("Europe/Berlin");
16636
- const serverLockedLocale = languagePolicy && !languagePolicy.multiLanguageEnabled
16637
- ? languagePolicy.organizationLocale
16638
- : undefined;
16639
- const i18nConfigLocale = serverLockedLocale !== undefined ? serverLockedLocale : props.config.locale;
16640
- const providerProps = i18nConfigLocale ? { configLocale: i18nConfigLocale } : {};
16700
+ const providerProps = props.config.locale ? { configLocale: props.config.locale } : {};
16641
16701
  const showLanguagePicker = props.config.showLanguagePicker !== false &&
16642
16702
  (languagePolicy === null || languagePolicy.multiLanguageEnabled);
16643
16703
  return (u$2(I18nProvider, { ...providerProps, children: u$2(ShowLanguagePickerProvider, { value: showLanguagePicker, children: u$2(TimezoneProvider, { value: orgTimezone, children: u$2(UniversalBookingWidgetInner, { ...props, onWidgetLanguage: setLanguagePolicy, onTimezone: setOrgTimezone }) }) }) }));