@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/booking-widget.js +233 -131
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts +0 -7
- 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 +3 -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 +233 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +234 -132
- 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/utils/google-ads-tracking.d.ts +9 -19
- package/dist/utils/google-ads-tracking.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.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:
|
|
1625
|
-
//
|
|
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
|
|
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
|
|
11212
|
-
name:
|
|
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
|
-
|
|
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 *
|
|
11492
|
-
}, [eventDetails,
|
|
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((
|
|
11497
|
-
|
|
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
|
-
|
|
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((
|
|
11549
|
-
if (
|
|
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
|
|
11639
|
+
const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
|
|
11584
11640
|
const totalParticipantRows = watchedParticipants.length;
|
|
11585
|
-
const allParticipantsHaveNames =
|
|
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.
|
|
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.
|
|
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((
|
|
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
|
|
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 (
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
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
|
|
12031
|
+
* Google Ads Tracking Utility
|
|
11972
12032
|
*
|
|
11973
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12078
|
+
const parentGtag = window.parent?.gtag;
|
|
12079
|
+
if (typeof parentGtag === "function")
|
|
12080
|
+
return parentGtag;
|
|
12041
12081
|
}
|
|
12042
12082
|
catch {
|
|
12043
|
-
//
|
|
12083
|
+
// Cross-origin
|
|
12044
12084
|
}
|
|
12045
12085
|
}
|
|
12046
|
-
|
|
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
|
-
|
|
12050
|
-
|
|
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
|
-
|
|
12112
|
+
params.value = config.conversionValue;
|
|
12056
12113
|
}
|
|
12057
12114
|
if (config.conversionCurrency) {
|
|
12058
|
-
|
|
12115
|
+
params.currency = config.conversionCurrency;
|
|
12059
12116
|
}
|
|
12060
12117
|
if (config.transactionId) {
|
|
12061
|
-
|
|
12118
|
+
params.transaction_id = config.transactionId;
|
|
12062
12119
|
}
|
|
12063
|
-
|
|
12064
|
-
gtag("event", "conversion", conversionData);
|
|
12120
|
+
sendEvent("conversion", params);
|
|
12065
12121
|
}
|
|
12066
12122
|
/**
|
|
12067
|
-
*
|
|
12068
|
-
* Waits 1500ms, checks/initializes gtag, then sends conversion
|
|
12123
|
+
* Track widget pageview (fired once on widget mount).
|
|
12069
12124
|
*/
|
|
12070
|
-
function
|
|
12071
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
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
|
-
|
|
12136
|
-
|
|
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:
|
|
12144
|
-
conversionId:
|
|
12145
|
-
conversionValue,
|
|
12146
|
-
conversionCurrency:
|
|
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
|
|
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 }) }))
|
|
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
|
|
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 }) }) }) }));
|