@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.
- package/dist/booking-widget.js +116 -56
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- package/dist/components/booking/BookingForm.d.ts +3 -0
- package/dist/components/booking/BookingForm.d.ts.map +1 -1
- package/dist/components/booking/BookingSuccessModal.d.ts.map +1 -1
- package/dist/i18n/i18n-context.d.ts.map +1 -1
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/index.cjs +116 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +117 -57
- package/dist/index.esm.js.map +1 -1
- package/dist/styles/StyleProvider.d.ts.map +1 -1
- package/dist/styles/shared-styles.d.ts.map +1 -1
- package/dist/validation/booking-schema.d.ts +54 -13
- package/dist/validation/booking-schema.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import React__default, { createContext, useState,
|
|
2
|
+
import React__default, { createContext, useState, useCallback, useMemo, useContext, useEffect, forwardRef, useRef, Fragment as Fragment$1 } from 'react';
|
|
3
3
|
import { createRoot } from 'react-dom/client';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
@@ -280,6 +280,8 @@ const de$1 = {
|
|
|
280
280
|
"booking.participantName": "Name *",
|
|
281
281
|
"booking.participantNamePlaceholder": "Teilnehmername",
|
|
282
282
|
"booking.participantAge": "Alter",
|
|
283
|
+
"booking.participantLevel": "Level",
|
|
284
|
+
"booking.participantLevelPlaceholder": "Level wählen...",
|
|
283
285
|
"booking.addParticipant": "{{number}}. Teilnehmer hinzufügen",
|
|
284
286
|
"booking.maxParticipants": "Maximale Anzahl an Teilnehmern erreicht. Es sind nur noch {{count}} Plätze verfügbar.",
|
|
285
287
|
"booking.maxSpotsReached": "Maximal {{count}} Plätze verfügbar.",
|
|
@@ -428,7 +430,11 @@ const de$1 = {
|
|
|
428
430
|
"validation.emailInvalid": "Ungültiges E-Mail-Format",
|
|
429
431
|
"validation.emailDomainInvalid": "Ungültige E-Mail-Domain",
|
|
430
432
|
"validation.participantRequired": "Mindestens ein Teilnehmer erforderlich",
|
|
433
|
+
"validation.ageRequired": "Alter ist erforderlich",
|
|
434
|
+
"validation.levelRequired": "Bitte ein Level auswählen",
|
|
431
435
|
"validation.acceptTerms": "Bitte akzeptiere die Allgemeinen Geschäftsbedingungen",
|
|
436
|
+
"level.beginner": "Anfänger",
|
|
437
|
+
"level.advanced": "Fortgeschritten",
|
|
432
438
|
// Sidebar
|
|
433
439
|
"sidebar.close": "Schließen",
|
|
434
440
|
// Promo
|
|
@@ -550,6 +556,8 @@ const en = {
|
|
|
550
556
|
"booking.participantName": "Name *",
|
|
551
557
|
"booking.participantNamePlaceholder": "Participant name",
|
|
552
558
|
"booking.participantAge": "Age",
|
|
559
|
+
"booking.participantLevel": "Level",
|
|
560
|
+
"booking.participantLevelPlaceholder": "Select level...",
|
|
553
561
|
"booking.addParticipant": "Add participant {{number}}",
|
|
554
562
|
"booking.maxParticipants": "Maximum number of participants reached. Only {{count}} spots are available.",
|
|
555
563
|
"booking.maxSpotsReached": "Maximum {{count}} spots available.",
|
|
@@ -698,7 +706,11 @@ const en = {
|
|
|
698
706
|
"validation.emailInvalid": "Invalid email format",
|
|
699
707
|
"validation.emailDomainInvalid": "Invalid email domain",
|
|
700
708
|
"validation.participantRequired": "At least one participant is required",
|
|
709
|
+
"validation.ageRequired": "Age is required",
|
|
710
|
+
"validation.levelRequired": "Please select a level",
|
|
701
711
|
"validation.acceptTerms": "Please accept the terms and conditions",
|
|
712
|
+
"level.beginner": "Beginner",
|
|
713
|
+
"level.advanced": "Advanced",
|
|
702
714
|
// Sidebar
|
|
703
715
|
"sidebar.close": "Close",
|
|
704
716
|
// Promo
|
|
@@ -820,6 +832,8 @@ const es = {
|
|
|
820
832
|
"booking.participantName": "Nombre *",
|
|
821
833
|
"booking.participantNamePlaceholder": "Nombre del participante",
|
|
822
834
|
"booking.participantAge": "Edad",
|
|
835
|
+
"booking.participantLevel": "Nivel",
|
|
836
|
+
"booking.participantLevelPlaceholder": "Seleccionar nivel...",
|
|
823
837
|
"booking.addParticipant": "Añadir participante {{number}}",
|
|
824
838
|
"booking.maxParticipants": "Número máximo de participantes alcanzado. Solo quedan {{count}} plazas disponibles.",
|
|
825
839
|
"booking.maxSpotsReached": "Máximo {{count}} plazas disponibles.",
|
|
@@ -968,7 +982,11 @@ const es = {
|
|
|
968
982
|
"validation.emailInvalid": "Formato de correo electrónico inválido",
|
|
969
983
|
"validation.emailDomainInvalid": "Dominio de correo electrónico inválido",
|
|
970
984
|
"validation.participantRequired": "Se requiere al menos un participante",
|
|
985
|
+
"validation.ageRequired": "La edad es obligatoria",
|
|
986
|
+
"validation.levelRequired": "Selecciona un nivel",
|
|
971
987
|
"validation.acceptTerms": "Por favor, acepta los términos y condiciones",
|
|
988
|
+
"level.beginner": "Principiante",
|
|
989
|
+
"level.advanced": "Avanzado",
|
|
972
990
|
// Sidebar
|
|
973
991
|
"sidebar.close": "Cerrar",
|
|
974
992
|
// Promo
|
|
@@ -1090,6 +1108,8 @@ const pt = {
|
|
|
1090
1108
|
"booking.participantName": "Nome *",
|
|
1091
1109
|
"booking.participantNamePlaceholder": "Nome do participante",
|
|
1092
1110
|
"booking.participantAge": "Idade",
|
|
1111
|
+
"booking.participantLevel": "Nível",
|
|
1112
|
+
"booking.participantLevelPlaceholder": "Selecionar nível...",
|
|
1093
1113
|
"booking.addParticipant": "Adicionar participante {{number}}",
|
|
1094
1114
|
"booking.maxParticipants": "Número máximo de participantes atingido. Apenas {{count}} lugares disponíveis.",
|
|
1095
1115
|
"booking.maxSpotsReached": "Máximo {{count}} lugares disponíveis.",
|
|
@@ -1238,7 +1258,11 @@ const pt = {
|
|
|
1238
1258
|
"validation.emailInvalid": "Formato de email inválido",
|
|
1239
1259
|
"validation.emailDomainInvalid": "Domínio de email inválido",
|
|
1240
1260
|
"validation.participantRequired": "É necessário pelo menos um participante",
|
|
1261
|
+
"validation.ageRequired": "A idade é obrigatória",
|
|
1262
|
+
"validation.levelRequired": "Por favor selecione um nível",
|
|
1241
1263
|
"validation.acceptTerms": "Por favor, aceite os termos e condições",
|
|
1264
|
+
"level.beginner": "Iniciante",
|
|
1265
|
+
"level.advanced": "Avançado",
|
|
1242
1266
|
// Sidebar
|
|
1243
1267
|
"sidebar.close": "Fechar",
|
|
1244
1268
|
// Promo
|
|
@@ -1360,6 +1384,8 @@ const sv = {
|
|
|
1360
1384
|
"booking.participantName": "Namn *",
|
|
1361
1385
|
"booking.participantNamePlaceholder": "Deltagarens namn",
|
|
1362
1386
|
"booking.participantAge": "Ålder",
|
|
1387
|
+
"booking.participantLevel": "Nivå",
|
|
1388
|
+
"booking.participantLevelPlaceholder": "Välj nivå...",
|
|
1363
1389
|
"booking.addParticipant": "Lägg till deltagare {{number}}",
|
|
1364
1390
|
"booking.maxParticipants": "Maximalt antal deltagare uppnått. Bara {{count}} platser är tillgängliga.",
|
|
1365
1391
|
"booking.maxSpotsReached": "Maximalt {{count}} platser tillgängliga.",
|
|
@@ -1508,7 +1534,11 @@ const sv = {
|
|
|
1508
1534
|
"validation.emailInvalid": "Ogiltigt e-postformat",
|
|
1509
1535
|
"validation.emailDomainInvalid": "Ogiltig e-postdomän",
|
|
1510
1536
|
"validation.participantRequired": "Minst en deltagare krävs",
|
|
1537
|
+
"validation.ageRequired": "Ålder krävs",
|
|
1538
|
+
"validation.levelRequired": "Välj en nivå",
|
|
1511
1539
|
"validation.acceptTerms": "Acceptera villkoren",
|
|
1540
|
+
"level.beginner": "Nybörjare",
|
|
1541
|
+
"level.advanced": "Avancerad",
|
|
1512
1542
|
// Sidebar
|
|
1513
1543
|
"sidebar.close": "Stäng",
|
|
1514
1544
|
// Promo
|
|
@@ -1601,18 +1631,9 @@ function persistLocale(locale) {
|
|
|
1601
1631
|
}
|
|
1602
1632
|
const I18nContext = createContext(null);
|
|
1603
1633
|
function I18nProvider({ configLocale, children }) {
|
|
1604
|
-
// Priority:
|
|
1605
|
-
//
|
|
1606
|
-
const [overrideLocale, setOverrideLocale] = useState(() =>
|
|
1607
|
-
if (configLocale)
|
|
1608
|
-
return null;
|
|
1609
|
-
return readPersistedLocale();
|
|
1610
|
-
});
|
|
1611
|
-
useEffect(() => {
|
|
1612
|
-
if (configLocale) {
|
|
1613
|
-
setOverrideLocale(null);
|
|
1614
|
-
}
|
|
1615
|
-
}, [configLocale]);
|
|
1634
|
+
// Priority: persisted user choice > configLocale (org default) > browser language > "de"
|
|
1635
|
+
// This keeps org locale as default, but remembers explicit user overrides across reloads.
|
|
1636
|
+
const [overrideLocale, setOverrideLocale] = useState(() => readPersistedLocale());
|
|
1616
1637
|
const locale = overrideLocale ?? resolveLocale(configLocale);
|
|
1617
1638
|
const handleSetLocale = useCallback((next) => {
|
|
1618
1639
|
persistLocale(next);
|
|
@@ -1744,6 +1765,16 @@ const resolveSemanticColor = (colorValue, fallbackValue) => {
|
|
|
1744
1765
|
// If semantic resolution fails, use fallback or return the original value
|
|
1745
1766
|
return fallbackValue || colorValue;
|
|
1746
1767
|
};
|
|
1768
|
+
// Legacy theme name redirects (old name → new name)
|
|
1769
|
+
const legacyThemeRedirects = {
|
|
1770
|
+
"light-fresh": "teal-minimal",
|
|
1771
|
+
"light-elegant": "blue-business",
|
|
1772
|
+
"light-vibrant": "orange-raw",
|
|
1773
|
+
"light-professional": "blue-business",
|
|
1774
|
+
"dark-night": "navy-night",
|
|
1775
|
+
"dark-modern": "navy-night",
|
|
1776
|
+
"dark-forest": "green-deep",
|
|
1777
|
+
};
|
|
1747
1778
|
// Predefined themes
|
|
1748
1779
|
const themes = {
|
|
1749
1780
|
// --- Light Themes ---
|
|
@@ -1890,7 +1921,9 @@ const StyleProvider = ({ config, children, }) => {
|
|
|
1890
1921
|
}, []);
|
|
1891
1922
|
// PERFORMANCE OPTIMIZATION: Memoize style calculations
|
|
1892
1923
|
const themedStyles = useMemo(() => {
|
|
1893
|
-
const
|
|
1924
|
+
const rawThemeName = config.theme || "teal-minimal";
|
|
1925
|
+
// Redirect legacy theme names to new names
|
|
1926
|
+
const themeName = legacyThemeRedirects[rawThemeName] || rawThemeName;
|
|
1894
1927
|
const themeDefaults = themes[themeName] || themes["teal-minimal"];
|
|
1895
1928
|
const getCSSValue = (value, fallback) => {
|
|
1896
1929
|
if (!value)
|
|
@@ -11183,16 +11216,37 @@ const objectType = ZodObject.create;
|
|
|
11183
11216
|
ZodUnion.create;
|
|
11184
11217
|
ZodIntersection.create;
|
|
11185
11218
|
ZodTuple.create;
|
|
11186
|
-
ZodEnum.create;
|
|
11219
|
+
const enumType = ZodEnum.create;
|
|
11187
11220
|
ZodPromise.create;
|
|
11188
11221
|
ZodOptional.create;
|
|
11189
11222
|
ZodNullable.create;
|
|
11223
|
+
const preprocessType = ZodEffects.createWithPreprocess;
|
|
11190
11224
|
|
|
11191
|
-
const
|
|
11192
|
-
name:
|
|
11225
|
+
const DEFAULT_PARTICIPANT_FIELDS_CONFIG = {
|
|
11226
|
+
name: { enabled: true, required: true },
|
|
11227
|
+
age: { enabled: true, required: false },
|
|
11228
|
+
level: { enabled: false, required: false },
|
|
11229
|
+
};
|
|
11230
|
+
const participantSchema = (t, fieldsConfig) => objectType({
|
|
11231
|
+
name: stringType().trim().optional(),
|
|
11193
11232
|
age: numberType().min(0).max(120).optional(),
|
|
11233
|
+
level: preprocessType((value) => (value === "" ? undefined : value), enumType(["beginner", "advanced"]).optional()),
|
|
11234
|
+
})
|
|
11235
|
+
.superRefine((value, ctx) => {
|
|
11236
|
+
if (fieldsConfig.name.required && (!value.name || value.name.trim().length < 1)) {
|
|
11237
|
+
ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
|
|
11238
|
+
}
|
|
11239
|
+
if (!fieldsConfig.name.enabled && value.name && value.name.trim().length > 0) {
|
|
11240
|
+
ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
|
|
11241
|
+
}
|
|
11242
|
+
if (fieldsConfig.age.required && typeof value.age !== "number") {
|
|
11243
|
+
ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.ageRequired"), path: ["age"] });
|
|
11244
|
+
}
|
|
11245
|
+
if (fieldsConfig.level.required && !value.level) {
|
|
11246
|
+
ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.levelRequired"), path: ["level"] });
|
|
11247
|
+
}
|
|
11194
11248
|
});
|
|
11195
|
-
function createBookingFormSchema(t) {
|
|
11249
|
+
function createBookingFormSchema(t, fieldsConfig = DEFAULT_PARTICIPANT_FIELDS_CONFIG) {
|
|
11196
11250
|
const tr = t ?? ((key) => key);
|
|
11197
11251
|
return objectType({
|
|
11198
11252
|
customerName: stringType().trim().min(2, tr("validation.nameMinLength")),
|
|
@@ -11202,7 +11256,7 @@ function createBookingFormSchema(t) {
|
|
|
11202
11256
|
.email(tr("validation.emailInvalid"))
|
|
11203
11257
|
.regex(/\.[a-zA-Z]{2,}$/, tr("validation.emailDomainInvalid")),
|
|
11204
11258
|
customerPhone: stringType().trim().optional(),
|
|
11205
|
-
participants: arrayType(participantSchema(tr)).min(1, tr("validation.participantRequired")),
|
|
11259
|
+
participants: arrayType(participantSchema(tr, fieldsConfig)).min(1, tr("validation.participantRequired")),
|
|
11206
11260
|
discountCode: stringType().trim().optional(),
|
|
11207
11261
|
comment: stringType().trim().optional(),
|
|
11208
11262
|
acceptTerms: booleanType().refine((val) => val === true, {
|
|
@@ -11345,7 +11399,8 @@ const participantUpsellStyles = {
|
|
|
11345
11399
|
gap: "8px",
|
|
11346
11400
|
marginTop: "10px",
|
|
11347
11401
|
paddingTop: "10px",
|
|
11348
|
-
|
|
11402
|
+
paddingBottom: "25px",
|
|
11403
|
+
borderBottom: "1px dashed var(--bw-border-color)",
|
|
11349
11404
|
},
|
|
11350
11405
|
label: {
|
|
11351
11406
|
display: "inline-flex",
|
|
@@ -11403,6 +11458,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11403
11458
|
const { locale } = useLocale();
|
|
11404
11459
|
const timezone = useTimezone();
|
|
11405
11460
|
const roundEnabled = systemConfig?.roundPricesEnabled !== false;
|
|
11461
|
+
const participantFieldsConfig = eventDetails.participantFieldsConfig ?? DEFAULT_PARTICIPANT_FIELDS_CONFIG;
|
|
11462
|
+
const participantLevelOptions = eventDetails.participantLevelOptions ?? ["beginner", "advanced"];
|
|
11406
11463
|
const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
|
|
11407
11464
|
const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
|
|
11408
11465
|
const raw = Math.round((baseAmount * basisPoints) / 10000);
|
|
@@ -11415,18 +11472,19 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11415
11472
|
// Per-participant upsell selections: participantIndex -> array of upsell package IDs
|
|
11416
11473
|
const [participantUpsells, setParticipantUpsells] = useState({});
|
|
11417
11474
|
const form = useForm({
|
|
11418
|
-
resolver: t(createBookingFormSchema(t$1)),
|
|
11475
|
+
resolver: t(createBookingFormSchema(t$1, participantFieldsConfig)),
|
|
11419
11476
|
defaultValues: {
|
|
11420
11477
|
customerName: "",
|
|
11421
11478
|
customerEmail: "",
|
|
11422
11479
|
customerPhone: "",
|
|
11423
|
-
participants: [{ name: "" }],
|
|
11480
|
+
participants: [{ name: "", level: undefined }],
|
|
11424
11481
|
discountCode: "",
|
|
11425
11482
|
comment: "",
|
|
11426
11483
|
acceptTerms: false,
|
|
11427
11484
|
},
|
|
11428
11485
|
});
|
|
11429
11486
|
const watchedParticipants = form.watch("participants");
|
|
11487
|
+
const participantCount = watchedParticipants.length;
|
|
11430
11488
|
const watchedCustomerName = form.watch("customerName");
|
|
11431
11489
|
const watchedCustomerEmail = form.watch("customerEmail");
|
|
11432
11490
|
const watchedComment = form.watch("comment");
|
|
@@ -11468,14 +11526,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11468
11526
|
const calculateBaseTotal = useCallback(() => {
|
|
11469
11527
|
if (!eventDetails)
|
|
11470
11528
|
return 0;
|
|
11471
|
-
return eventDetails.price *
|
|
11472
|
-
}, [eventDetails,
|
|
11529
|
+
return eventDetails.price * participantCount;
|
|
11530
|
+
}, [eventDetails, participantCount]);
|
|
11473
11531
|
// Calculate upsells total based on per-participant selections
|
|
11474
11532
|
const calculateUpsellsTotal = useCallback(() => {
|
|
11475
11533
|
let total = 0;
|
|
11476
|
-
watchedParticipants.forEach((
|
|
11477
|
-
|
|
11478
|
-
if (participant.name.trim()) {
|
|
11534
|
+
watchedParticipants.forEach((_, index) => {
|
|
11535
|
+
if (participantCount > 0) {
|
|
11479
11536
|
const participantUpsellIds = participantUpsells[index] || [];
|
|
11480
11537
|
participantUpsellIds.forEach(upsellId => {
|
|
11481
11538
|
const upsell = upsells.find(u => u.id === upsellId);
|
|
@@ -11486,7 +11543,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11486
11543
|
}
|
|
11487
11544
|
});
|
|
11488
11545
|
return total;
|
|
11489
|
-
}, [participantUpsells, upsells, watchedParticipants]);
|
|
11546
|
+
}, [participantUpsells, upsells, watchedParticipants, participantCount]);
|
|
11490
11547
|
const calculateTotalDiscount = useCallback(() => {
|
|
11491
11548
|
return appliedVouchers.reduce((total, voucher) => {
|
|
11492
11549
|
if (voucher.type === "discount") {
|
|
@@ -11507,8 +11564,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11507
11564
|
const calculateDeposit = () => {
|
|
11508
11565
|
if (!eventDetails || !eventDetails.deposit)
|
|
11509
11566
|
return 0;
|
|
11510
|
-
|
|
11511
|
-
return eventDetails.deposit * participantCount;
|
|
11567
|
+
return eventDetails.deposit * watchedParticipants.length;
|
|
11512
11568
|
};
|
|
11513
11569
|
const baseTotal = calculateBaseTotal();
|
|
11514
11570
|
const upsellsTotal = calculateUpsellsTotal();
|
|
@@ -11525,8 +11581,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11525
11581
|
// Includes participantIndices to track which participants selected each upsell
|
|
11526
11582
|
const aggregatedUpsellSelections = useCallback(() => {
|
|
11527
11583
|
const upsellParticipantMap = {};
|
|
11528
|
-
watchedParticipants.forEach((
|
|
11529
|
-
if (
|
|
11584
|
+
watchedParticipants.forEach((_, index) => {
|
|
11585
|
+
if (participantCount > 0) {
|
|
11530
11586
|
const participantUpsellIds = participantUpsells[index] || [];
|
|
11531
11587
|
participantUpsellIds.forEach(upsellId => {
|
|
11532
11588
|
if (!upsellParticipantMap[upsellId]) {
|
|
@@ -11560,15 +11616,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11560
11616
|
setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
|
|
11561
11617
|
}, []);
|
|
11562
11618
|
const isReadyForPayment = () => {
|
|
11563
|
-
const participantsWithNames = watchedParticipants.filter((p) => p.name
|
|
11619
|
+
const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
|
|
11564
11620
|
const totalParticipantRows = watchedParticipants.length;
|
|
11565
|
-
const allParticipantsHaveNames =
|
|
11621
|
+
const allParticipantsHaveNames = participantFieldsConfig.name.required
|
|
11622
|
+
? participantsWithNames === totalParticipantRows
|
|
11623
|
+
: true;
|
|
11566
11624
|
const participantsWithinLimit = participantsWithNames <= (eventDetails?.availableSpots || 0);
|
|
11567
11625
|
const hasValidCustomerName = watchedCustomerName && watchedCustomerName.trim().length >= 2;
|
|
11568
11626
|
const hasValidCustomerEmail = watchedCustomerEmail && watchedCustomerEmail.trim().length > 0 && !customerEmailError;
|
|
11569
11627
|
return allParticipantsHaveNames &&
|
|
11570
11628
|
participantsWithinLimit &&
|
|
11571
|
-
participantsWithNames > 0 &&
|
|
11629
|
+
(participantFieldsConfig.name.required ? participantsWithNames > 0 : totalParticipantRows > 0) &&
|
|
11572
11630
|
hasValidCustomerName &&
|
|
11573
11631
|
hasValidCustomerEmail &&
|
|
11574
11632
|
watchedAcceptTerms;
|
|
@@ -11576,7 +11634,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11576
11634
|
useEffect(() => {
|
|
11577
11635
|
if (appliedVouchers.length > 0) {
|
|
11578
11636
|
const newBaseTotal = eventDetails?.price
|
|
11579
|
-
? eventDetails.price * watchedParticipants.
|
|
11637
|
+
? eventDetails.price * watchedParticipants.length
|
|
11580
11638
|
: 0;
|
|
11581
11639
|
const currentUpsellsTotal = calculateUpsellsTotal();
|
|
11582
11640
|
const orderTotal = newBaseTotal + currentUpsellsTotal;
|
|
@@ -11621,7 +11679,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11621
11679
|
const currentParticipants = form.getValues("participants");
|
|
11622
11680
|
const availableSpots = eventDetails?.availableSpots || 0;
|
|
11623
11681
|
if (currentParticipants.length < availableSpots) {
|
|
11624
|
-
form.setValue("participants", [...currentParticipants, { name: "" }]);
|
|
11682
|
+
form.setValue("participants", [...currentParticipants, { name: "", level: undefined }]);
|
|
11625
11683
|
}
|
|
11626
11684
|
else {
|
|
11627
11685
|
alert(t$1("booking.maxParticipants", { count: availableSpots }));
|
|
@@ -11740,7 +11798,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11740
11798
|
justifyContent: "space-between",
|
|
11741
11799
|
alignItems: "center",
|
|
11742
11800
|
marginBottom: "16px",
|
|
11743
|
-
}, children: jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [jsxs("div", { style: { flex: 1 }, children: [jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), 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 && (jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] }), jsxs("div", { style: { width: "80px" }, children: [jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
|
|
11801
|
+
}, children: jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [participantFieldsConfig.name.enabled && (jsxs("div", { style: { flex: 1 }, children: [jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), 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 && (jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] })), participantFieldsConfig.age.enabled && (jsxs("div", { style: { width: "80px" }, children: [jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
|
|
11744
11802
|
setValueAs: (value) => {
|
|
11745
11803
|
if (value === "" || value === null || value === undefined) {
|
|
11746
11804
|
return undefined;
|
|
@@ -11748,7 +11806,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11748
11806
|
const num = Number(value);
|
|
11749
11807
|
return Number.isNaN(num) ? undefined : num;
|
|
11750
11808
|
},
|
|
11751
|
-
}), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] }), watchedParticipants.length > 1 && (jsxs("div", { children: [jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
|
|
11809
|
+
}), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] })), watchedParticipants.length > 1 && (jsxs("div", { children: [jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
|
|
11752
11810
|
color: "var(--bw-error-color)",
|
|
11753
11811
|
backgroundColor: "var(--bw-surface-color)",
|
|
11754
11812
|
border: "1px solid var(--bw-border-color)",
|
|
@@ -11764,7 +11822,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11764
11822
|
fontWeight: 700,
|
|
11765
11823
|
fontFamily: "var(--bw-font-family)",
|
|
11766
11824
|
padding: 0,
|
|
11767
|
-
}, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
|
|
11825
|
+
}, children: "\u00D7" })] }))] }), participantFieldsConfig.level.enabled && (jsxs("div", { style: { minWidth: "140px" }, children: [jsx("label", { htmlFor: `participant-level-${index}`, style: labelStyles$1, children: t$1("booking.participantLevel") }), jsxs("select", { id: `participant-level-${index}`, ...form.register(`participants.${index}.level`), style: inputStyles$1, children: [jsx("option", { value: "", children: t$1("booking.participantLevelPlaceholder") }), participantLevelOptions.map((level) => (jsx("option", { value: level, children: t$1(`level.${level}`) }, level)))] }), form.formState.errors.participants?.[index]?.level && (jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.level?.message }))] })), upsells.length > 0 && (jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
|
|
11768
11826
|
const isSelected = (participantUpsells[index] || []).includes(upsell.id);
|
|
11769
11827
|
return (jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
|
|
11770
11828
|
}) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsx("div", { style: {
|
|
@@ -11793,9 +11851,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11793
11851
|
color: "var(--bw-text-color)",
|
|
11794
11852
|
fontWeight: 500,
|
|
11795
11853
|
fontFamily: "var(--bw-font-family)",
|
|
11796
|
-
}, children: [jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.
|
|
11854
|
+
}, children: [jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [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) => {
|
|
11797
11855
|
// Count how many participants have this upsell selected
|
|
11798
|
-
const countWithUpsell = watchedParticipants.filter((
|
|
11856
|
+
const countWithUpsell = watchedParticipants.filter((_, idx) => (participantUpsells[idx] || []).includes(upsell.id)).length;
|
|
11799
11857
|
if (countWithUpsell === 0)
|
|
11800
11858
|
return null;
|
|
11801
11859
|
const upsellLineTotal = upsell.price * countWithUpsell;
|
|
@@ -11896,15 +11954,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11896
11954
|
}, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), jsx("div", { ref: paymentSectionRef, children: (stripePromise || systemConfig?.paymentProvider === "mollie") &&
|
|
11897
11955
|
(() => {
|
|
11898
11956
|
if (!isReadyForPayment()) {
|
|
11899
|
-
const participantsWithNames = watchedParticipants.filter((p) => p.name
|
|
11957
|
+
const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
|
|
11900
11958
|
const totalParticipantRows = watchedParticipants.length;
|
|
11901
11959
|
const participantsWithoutNames = totalParticipantRows - participantsWithNames;
|
|
11902
11960
|
const missing = [];
|
|
11903
|
-
if (
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11961
|
+
if (participantFieldsConfig.name.required) {
|
|
11962
|
+
if (participantsWithNames === 0) {
|
|
11963
|
+
missing.push(t$1("payment.needParticipant"));
|
|
11964
|
+
}
|
|
11965
|
+
else if (participantsWithoutNames > 0) {
|
|
11966
|
+
missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
|
|
11967
|
+
}
|
|
11908
11968
|
}
|
|
11909
11969
|
if (participantsWithNames > (eventDetails?.availableSpots || 0)) {
|
|
11910
11970
|
missing.push(t$1("payment.reduceParticipants", { count: eventDetails?.availableSpots || 0 }));
|
|
@@ -12079,7 +12139,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12079
12139
|
try {
|
|
12080
12140
|
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
|
|
12081
12141
|
method: "POST",
|
|
12082
|
-
headers: createApiHeaders(config),
|
|
12142
|
+
headers: createApiHeaders(config, locale),
|
|
12083
12143
|
body: JSON.stringify(createRequestBody(config, {
|
|
12084
12144
|
paymentIntentId: targetPaymentIntentId,
|
|
12085
12145
|
})),
|
|
@@ -12313,7 +12373,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12313
12373
|
flexDirection: "column",
|
|
12314
12374
|
gap: "var(--bw-spacing-small)",
|
|
12315
12375
|
}, children: formData.participants
|
|
12316
|
-
.filter((p) => p.name
|
|
12376
|
+
.filter((p) => p.name?.trim() || p.age || p.level)
|
|
12317
12377
|
.map((participant, index) => (jsx("div", { className: "print-participant", style: {
|
|
12318
12378
|
display: "flex",
|
|
12319
12379
|
justifyContent: "space-between",
|
|
@@ -12324,11 +12384,15 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12324
12384
|
}, children: jsxs("div", { className: "print-participant-info", children: [jsx("div", { className: "print-participant-name", style: {
|
|
12325
12385
|
color: "var(--bw-text-color)",
|
|
12326
12386
|
fontFamily: "var(--bw-font-family)",
|
|
12327
|
-
}, children: participant.name }), participant.age && (jsx("div", { className: "print-participant-age", style: {
|
|
12387
|
+
}, children: participant.name || `#${index + 1}` }), participant.age && (jsx("div", { className: "print-participant-age", style: {
|
|
12328
12388
|
color: "var(--bw-text-muted)",
|
|
12329
12389
|
fontSize: "var(--bw-font-size-small)",
|
|
12330
12390
|
fontFamily: "var(--bw-font-family)",
|
|
12331
|
-
}, children: t("success.age", { age: participant.age }) }))
|
|
12391
|
+
}, children: t("success.age", { age: participant.age }) })), participant.level && (jsxs("div", { style: {
|
|
12392
|
+
color: "var(--bw-text-muted)",
|
|
12393
|
+
fontSize: "var(--bw-font-size-small)",
|
|
12394
|
+
fontFamily: "var(--bw-font-family)",
|
|
12395
|
+
}, children: [t("booking.participantLevel"), ": ", t(`level.${participant.level}`)] }))] }) }, index))) }) })] })), jsxs("div", { className: "print-booking-card", style: {
|
|
12332
12396
|
backgroundColor: "var(--bw-surface-color)",
|
|
12333
12397
|
border: `1px solid var(--bw-border-color)`,
|
|
12334
12398
|
borderRadius: "var(--bw-border-radius)",
|
|
@@ -16507,11 +16571,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16507
16571
|
function UniversalBookingWidget(props) {
|
|
16508
16572
|
const [languagePolicy, setLanguagePolicy] = useState(null);
|
|
16509
16573
|
const [orgTimezone, setOrgTimezone] = useState("Europe/Berlin");
|
|
16510
|
-
const
|
|
16511
|
-
? languagePolicy.organizationLocale
|
|
16512
|
-
: undefined;
|
|
16513
|
-
const i18nConfigLocale = serverLockedLocale !== undefined ? serverLockedLocale : props.config.locale;
|
|
16514
|
-
const providerProps = i18nConfigLocale ? { configLocale: i18nConfigLocale } : {};
|
|
16574
|
+
const providerProps = props.config.locale ? { configLocale: props.config.locale } : {};
|
|
16515
16575
|
const showLanguagePicker = props.config.showLanguagePicker !== false &&
|
|
16516
16576
|
(languagePolicy === null || languagePolicy.multiLanguageEnabled);
|
|
16517
16577
|
return (jsx(I18nProvider, { ...providerProps, children: jsx(ShowLanguagePickerProvider, { value: showLanguagePicker, children: jsx(TimezoneProvider, { value: orgTimezone, children: jsx(UniversalBookingWidgetInner, { ...props, onWidgetLanguage: setLanguagePolicy, onTimezone: setOrgTimezone }) }) }) }));
|