@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.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 }));
|
|
@@ -11948,121 +12008,145 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
11948
12008
|
}
|
|
11949
12009
|
|
|
11950
12010
|
/**
|
|
11951
|
-
* Google Ads
|
|
12011
|
+
* Google Ads Tracking Utility
|
|
11952
12012
|
*
|
|
11953
|
-
*
|
|
11954
|
-
|
|
11955
|
-
/**
|
|
11956
|
-
* Check if gtag is available in current or parent window
|
|
12013
|
+
* Handles pageview tracking (widget load) and conversion tracking (successful booking).
|
|
12014
|
+
* Supports both direct gtag.js and GTM dataLayer setups.
|
|
11957
12015
|
*/
|
|
11958
12016
|
function isGtagAvailable() {
|
|
11959
|
-
if (typeof window === "undefined")
|
|
12017
|
+
if (typeof window === "undefined")
|
|
11960
12018
|
return false;
|
|
11961
|
-
|
|
11962
|
-
// Check current window
|
|
11963
|
-
if (typeof window.gtag === "function") {
|
|
12019
|
+
if (typeof window.gtag === "function")
|
|
11964
12020
|
return true;
|
|
11965
|
-
}
|
|
11966
|
-
// Check parent window (for iframe/widget scenarios)
|
|
11967
12021
|
if (window !== window.parent) {
|
|
11968
12022
|
try {
|
|
11969
|
-
if (typeof window.parent?.gtag === "function")
|
|
12023
|
+
if (typeof window.parent?.gtag === "function")
|
|
11970
12024
|
return true;
|
|
11971
|
-
}
|
|
11972
12025
|
}
|
|
11973
12026
|
catch {
|
|
11974
|
-
//
|
|
12027
|
+
// Cross-origin
|
|
11975
12028
|
}
|
|
11976
12029
|
}
|
|
11977
12030
|
return false;
|
|
11978
12031
|
}
|
|
11979
|
-
/**
|
|
11980
|
-
* Initialize gtag if not already available
|
|
11981
|
-
*/
|
|
11982
12032
|
function initializeGtag(tagId) {
|
|
11983
|
-
if (typeof window === "undefined")
|
|
12033
|
+
if (typeof window === "undefined" || isGtagAvailable())
|
|
11984
12034
|
return;
|
|
11985
|
-
}
|
|
11986
|
-
// Skip if gtag already exists
|
|
11987
|
-
if (isGtagAvailable()) {
|
|
11988
|
-
return;
|
|
11989
|
-
}
|
|
11990
|
-
// Initialize dataLayer and gtag function
|
|
11991
12035
|
window.dataLayer = window.dataLayer || [];
|
|
11992
12036
|
window.gtag = (...args) => {
|
|
11993
12037
|
window.dataLayer.push(args);
|
|
11994
12038
|
};
|
|
11995
|
-
// Set current timestamp
|
|
11996
12039
|
window.gtag("js", new Date());
|
|
11997
|
-
// Load gtag script
|
|
11998
12040
|
const script = document.createElement("script");
|
|
11999
12041
|
script.async = true;
|
|
12000
12042
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${tagId}`;
|
|
12001
12043
|
document.head.appendChild(script);
|
|
12002
|
-
// Configure the tag
|
|
12003
12044
|
window.gtag("config", tagId, {
|
|
12004
12045
|
anonymize_ip: true,
|
|
12005
12046
|
allow_google_signals: false,
|
|
12006
12047
|
allow_ad_personalization_signals: false,
|
|
12007
12048
|
});
|
|
12008
12049
|
}
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
return;
|
|
12050
|
+
function getGtag() {
|
|
12051
|
+
if (typeof window === "undefined")
|
|
12052
|
+
return null;
|
|
12053
|
+
if (typeof window.gtag === "function") {
|
|
12054
|
+
return window.gtag;
|
|
12015
12055
|
}
|
|
12016
|
-
|
|
12017
|
-
// Try parent window gtag if current window doesn't have it
|
|
12018
|
-
if (typeof gtag !== "function" && window !== window.parent) {
|
|
12056
|
+
if (window !== window.parent) {
|
|
12019
12057
|
try {
|
|
12020
|
-
|
|
12058
|
+
const parentGtag = window.parent?.gtag;
|
|
12059
|
+
if (typeof parentGtag === "function")
|
|
12060
|
+
return parentGtag;
|
|
12021
12061
|
}
|
|
12022
12062
|
catch {
|
|
12023
|
-
//
|
|
12063
|
+
// Cross-origin
|
|
12024
12064
|
}
|
|
12025
12065
|
}
|
|
12026
|
-
|
|
12066
|
+
return null;
|
|
12067
|
+
}
|
|
12068
|
+
/**
|
|
12069
|
+
* Push an event to the dataLayer for GTM visibility,
|
|
12070
|
+
* then also fire via gtag for direct Google Ads tracking.
|
|
12071
|
+
*/
|
|
12072
|
+
function sendEvent(eventName, params) {
|
|
12073
|
+
if (typeof window === "undefined")
|
|
12027
12074
|
return;
|
|
12075
|
+
// GTM dataLayer push (object format — visible in Tag Assistant)
|
|
12076
|
+
window.dataLayer = window.dataLayer || [];
|
|
12077
|
+
window.dataLayer.push({
|
|
12078
|
+
event: eventName,
|
|
12079
|
+
...params,
|
|
12080
|
+
});
|
|
12081
|
+
// gtag call (array format — processed by gtag.js for Google Ads)
|
|
12082
|
+
const gtag = getGtag();
|
|
12083
|
+
if (gtag) {
|
|
12084
|
+
gtag("event", eventName, params);
|
|
12028
12085
|
}
|
|
12029
|
-
|
|
12030
|
-
|
|
12086
|
+
}
|
|
12087
|
+
function sendConversion(config) {
|
|
12088
|
+
const params = {
|
|
12031
12089
|
send_to: `${config.tagId}/${config.conversionId}`,
|
|
12032
12090
|
};
|
|
12033
|
-
// Add optional parameters
|
|
12034
12091
|
if (config.conversionValue !== undefined) {
|
|
12035
|
-
|
|
12092
|
+
params.value = config.conversionValue;
|
|
12036
12093
|
}
|
|
12037
12094
|
if (config.conversionCurrency) {
|
|
12038
|
-
|
|
12095
|
+
params.currency = config.conversionCurrency;
|
|
12039
12096
|
}
|
|
12040
12097
|
if (config.transactionId) {
|
|
12041
|
-
|
|
12098
|
+
params.transaction_id = config.transactionId;
|
|
12042
12099
|
}
|
|
12043
|
-
|
|
12044
|
-
gtag("event", "conversion", conversionData);
|
|
12100
|
+
sendEvent("conversion", params);
|
|
12045
12101
|
}
|
|
12046
12102
|
/**
|
|
12047
|
-
*
|
|
12048
|
-
* Waits 1500ms, checks/initializes gtag, then sends conversion
|
|
12103
|
+
* Track widget pageview (fired once on widget mount).
|
|
12049
12104
|
*/
|
|
12050
|
-
function
|
|
12051
|
-
|
|
12052
|
-
if (!config.tagId || !config.conversionId) {
|
|
12105
|
+
function handleGoogleAdsPageview(tagId, consent) {
|
|
12106
|
+
if (!tagId || false || typeof window === "undefined")
|
|
12053
12107
|
return;
|
|
12108
|
+
if (!isGtagAvailable()) {
|
|
12109
|
+
initializeGtag(tagId);
|
|
12054
12110
|
}
|
|
12055
|
-
|
|
12111
|
+
const fire = () => sendEvent("widget_pageview", {
|
|
12112
|
+
send_to: tagId,
|
|
12113
|
+
page_location: window.location.href,
|
|
12114
|
+
page_title: document.title,
|
|
12115
|
+
});
|
|
12116
|
+
if (isGtagAvailable()) {
|
|
12117
|
+
fire();
|
|
12118
|
+
return;
|
|
12119
|
+
}
|
|
12120
|
+
const script = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${tagId}"]`);
|
|
12121
|
+
if (script) {
|
|
12122
|
+
script.addEventListener("load", fire, { once: true });
|
|
12123
|
+
}
|
|
12124
|
+
}
|
|
12125
|
+
/**
|
|
12126
|
+
* Handle Google Ads conversion tracking.
|
|
12127
|
+
* Waits 1500ms for the success page to settle, then fires.
|
|
12128
|
+
*/
|
|
12129
|
+
function handleGoogleAdsConversion(config) {
|
|
12130
|
+
if (!config.tagId || !config.conversionId)
|
|
12131
|
+
return;
|
|
12056
12132
|
setTimeout(() => {
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12133
|
+
if (isGtagAvailable()) {
|
|
12134
|
+
sendConversion(config);
|
|
12135
|
+
return;
|
|
12136
|
+
}
|
|
12137
|
+
initializeGtag(config.tagId);
|
|
12138
|
+
const script = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${config.tagId}"]`);
|
|
12139
|
+
if (script) {
|
|
12140
|
+
script.addEventListener("load", () => sendConversion(config));
|
|
12141
|
+
script.addEventListener("error", () => sendConversion(config));
|
|
12142
|
+
}
|
|
12143
|
+
else {
|
|
12144
|
+
sendConversion(config);
|
|
12060
12145
|
}
|
|
12061
|
-
sendConversion(config);
|
|
12062
12146
|
}, 1500);
|
|
12063
12147
|
}
|
|
12064
12148
|
|
|
12065
|
-
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, }) => {
|
|
12149
|
+
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, googleAdsConfig: googleAdsConfigProp, }) => {
|
|
12066
12150
|
const t = useTranslations();
|
|
12067
12151
|
const { locale } = useLocale();
|
|
12068
12152
|
const timezone = useTimezone();
|
|
@@ -12079,7 +12163,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12079
12163
|
try {
|
|
12080
12164
|
const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
|
|
12081
12165
|
method: "POST",
|
|
12082
|
-
headers: createApiHeaders(config),
|
|
12166
|
+
headers: createApiHeaders(config, locale),
|
|
12083
12167
|
body: JSON.stringify(createRequestBody(config, {
|
|
12084
12168
|
paymentIntentId: targetPaymentIntentId,
|
|
12085
12169
|
})),
|
|
@@ -12111,20 +12195,16 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12111
12195
|
});
|
|
12112
12196
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
12113
12197
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
12198
|
+
const adsConfig = googleAdsConfigProp ?? data.googleAdsConfig;
|
|
12114
12199
|
if (finalPaymentStatus === "succeeded" &&
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
config.googleAds?.consent !== false) {
|
|
12118
|
-
// Prepare conversion tracking data
|
|
12119
|
-
const conversionValue = data.order.total / 100;
|
|
12120
|
-
const transactionId = data.order.id;
|
|
12121
|
-
// Track the conversion
|
|
12200
|
+
adsConfig?.tagId &&
|
|
12201
|
+
adsConfig?.conversionId) {
|
|
12122
12202
|
handleGoogleAdsConversion({
|
|
12123
|
-
tagId:
|
|
12124
|
-
conversionId:
|
|
12125
|
-
conversionValue,
|
|
12126
|
-
conversionCurrency:
|
|
12127
|
-
transactionId,
|
|
12203
|
+
tagId: adsConfig.tagId,
|
|
12204
|
+
conversionId: adsConfig.conversionId,
|
|
12205
|
+
conversionValue: data.order.total / 100,
|
|
12206
|
+
conversionCurrency: adsConfig.conversionCurrency || "EUR",
|
|
12207
|
+
transactionId: data.order.id,
|
|
12128
12208
|
});
|
|
12129
12209
|
}
|
|
12130
12210
|
}
|
|
@@ -12313,7 +12393,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12313
12393
|
flexDirection: "column",
|
|
12314
12394
|
gap: "var(--bw-spacing-small)",
|
|
12315
12395
|
}, children: formData.participants
|
|
12316
|
-
.filter((p) => p.name
|
|
12396
|
+
.filter((p) => p.name?.trim() || p.age || p.level)
|
|
12317
12397
|
.map((participant, index) => (jsx("div", { className: "print-participant", style: {
|
|
12318
12398
|
display: "flex",
|
|
12319
12399
|
justifyContent: "space-between",
|
|
@@ -12324,11 +12404,15 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12324
12404
|
}, children: jsxs("div", { className: "print-participant-info", children: [jsx("div", { className: "print-participant-name", style: {
|
|
12325
12405
|
color: "var(--bw-text-color)",
|
|
12326
12406
|
fontFamily: "var(--bw-font-family)",
|
|
12327
|
-
}, children: participant.name }), participant.age && (jsx("div", { className: "print-participant-age", style: {
|
|
12407
|
+
}, children: participant.name || `#${index + 1}` }), participant.age && (jsx("div", { className: "print-participant-age", style: {
|
|
12328
12408
|
color: "var(--bw-text-muted)",
|
|
12329
12409
|
fontSize: "var(--bw-font-size-small)",
|
|
12330
12410
|
fontFamily: "var(--bw-font-family)",
|
|
12331
|
-
}, children: t("success.age", { age: participant.age }) }))
|
|
12411
|
+
}, children: t("success.age", { age: participant.age }) })), participant.level && (jsxs("div", { style: {
|
|
12412
|
+
color: "var(--bw-text-muted)",
|
|
12413
|
+
fontSize: "var(--bw-font-size-small)",
|
|
12414
|
+
fontFamily: "var(--bw-font-family)",
|
|
12415
|
+
}, children: [t("booking.participantLevel"), ": ", t(`level.${participant.level}`)] }))] }) }, index))) }) })] })), jsxs("div", { className: "print-booking-card", style: {
|
|
12332
12416
|
backgroundColor: "var(--bw-surface-color)",
|
|
12333
12417
|
border: `1px solid var(--bw-border-color)`,
|
|
12334
12418
|
borderRadius: "var(--bw-border-radius)",
|
|
@@ -15385,6 +15469,13 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15385
15469
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
15386
15470
|
const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
|
|
15387
15471
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
|
|
15472
|
+
// Google Ads config (received from API, set once from the first API response)
|
|
15473
|
+
const [googleAdsConfig, setGoogleAdsConfig] = useState(null);
|
|
15474
|
+
const extractGoogleAdsConfig = (data) => {
|
|
15475
|
+
if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
|
|
15476
|
+
setGoogleAdsConfig(data.googleAdsConfig);
|
|
15477
|
+
}
|
|
15478
|
+
};
|
|
15388
15479
|
// Promo dialog state
|
|
15389
15480
|
const [showPromoDialog, setShowPromoDialog] = useState(false);
|
|
15390
15481
|
const [widgetContainerRef, setWidgetContainerRef] = useState(null);
|
|
@@ -15468,6 +15559,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15468
15559
|
image: resolvedImage,
|
|
15469
15560
|
};
|
|
15470
15561
|
setVoucherConfig(mergedConfig);
|
|
15562
|
+
extractGoogleAdsConfig(data);
|
|
15471
15563
|
setVoucherEventTypes(data.eventTypes || []);
|
|
15472
15564
|
// Set system config for payment processing
|
|
15473
15565
|
if (data.paymentProvider) {
|
|
@@ -15490,6 +15582,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15490
15582
|
setIsLoadingVoucherConfig(false);
|
|
15491
15583
|
}
|
|
15492
15584
|
};
|
|
15585
|
+
// Fire widget pageview once when Google Ads config is received from API
|
|
15586
|
+
const pageviewFiredRef = useRef(false);
|
|
15587
|
+
useEffect(() => {
|
|
15588
|
+
if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
|
|
15589
|
+
pageviewFiredRef.current = true;
|
|
15590
|
+
handleGoogleAdsPageview(googleAdsConfig.tagId);
|
|
15591
|
+
}
|
|
15592
|
+
}, [googleAdsConfig]);
|
|
15493
15593
|
// Determine initial step and load data
|
|
15494
15594
|
useEffect(() => {
|
|
15495
15595
|
const initializeWidget = async () => {
|
|
@@ -15703,6 +15803,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15703
15803
|
onWidgetLanguage?.(wl);
|
|
15704
15804
|
onTimezone?.(wl.timezone);
|
|
15705
15805
|
}
|
|
15806
|
+
extractGoogleAdsConfig(data);
|
|
15706
15807
|
setEventTypes(data.eventTypes);
|
|
15707
15808
|
if (isSingleEventTypeMode && data.eventTypes.length === 1) {
|
|
15708
15809
|
setSelectedEventType(data.eventTypes[0]);
|
|
@@ -15739,6 +15840,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15739
15840
|
onWidgetLanguage?.(wl);
|
|
15740
15841
|
onTimezone?.(wl.timezone);
|
|
15741
15842
|
}
|
|
15843
|
+
extractGoogleAdsConfig(data);
|
|
15742
15844
|
setUpcomingEvents(data.upcomingEvents || []);
|
|
15743
15845
|
}
|
|
15744
15846
|
else {
|
|
@@ -15774,6 +15876,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15774
15876
|
onWidgetLanguage?.(wl);
|
|
15775
15877
|
onTimezone?.(wl.timezone);
|
|
15776
15878
|
}
|
|
15879
|
+
extractGoogleAdsConfig(data);
|
|
15777
15880
|
setSpecials(data.specials || []);
|
|
15778
15881
|
}
|
|
15779
15882
|
else {
|
|
@@ -15800,6 +15903,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15800
15903
|
onWidgetLanguage?.(wl);
|
|
15801
15904
|
onTimezone?.(wl.timezone);
|
|
15802
15905
|
}
|
|
15906
|
+
extractGoogleAdsConfig(data);
|
|
15803
15907
|
setEventInstances(data.eventInstances);
|
|
15804
15908
|
if (data.paymentProvider) {
|
|
15805
15909
|
setSystemConfig({
|
|
@@ -15860,6 +15964,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15860
15964
|
onWidgetLanguage?.(wl);
|
|
15861
15965
|
onTimezone?.(wl.timezone);
|
|
15862
15966
|
}
|
|
15967
|
+
extractGoogleAdsConfig(data);
|
|
15863
15968
|
setEventDetails(data.eventDetails);
|
|
15864
15969
|
setSystemConfig({
|
|
15865
15970
|
paymentProvider: data.paymentProvider,
|
|
@@ -15929,6 +16034,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15929
16034
|
onWidgetLanguage?.(wl);
|
|
15930
16035
|
onTimezone?.(wl.timezone);
|
|
15931
16036
|
}
|
|
16037
|
+
extractGoogleAdsConfig(data);
|
|
15932
16038
|
return data.upsells || [];
|
|
15933
16039
|
}
|
|
15934
16040
|
else {
|
|
@@ -16342,7 +16448,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16342
16448
|
url.searchParams.delete("mollie_payment_id");
|
|
16343
16449
|
url.searchParams.delete("mollie_status");
|
|
16344
16450
|
window.history.replaceState({}, "", url.toString());
|
|
16345
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16451
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16346
16452
|
}
|
|
16347
16453
|
if (viewMode === "specials" && showingPreview) {
|
|
16348
16454
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => {
|
|
@@ -16367,7 +16473,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16367
16473
|
setShouldRenderBookingForm(false);
|
|
16368
16474
|
setSelectedUpsells([]);
|
|
16369
16475
|
setUpsells([]);
|
|
16370
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16476
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16371
16477
|
}
|
|
16372
16478
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
16373
16479
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -16390,7 +16496,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16390
16496
|
url.searchParams.delete("mollie_payment_id");
|
|
16391
16497
|
url.searchParams.delete("mollie_status");
|
|
16392
16498
|
window.history.replaceState({}, "", url.toString());
|
|
16393
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16499
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16394
16500
|
}
|
|
16395
16501
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
16396
16502
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -16436,7 +16542,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16436
16542
|
url.searchParams.delete("mollie_payment_id");
|
|
16437
16543
|
url.searchParams.delete("mollie_status");
|
|
16438
16544
|
window.history.replaceState({}, "", url.toString());
|
|
16439
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16545
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16440
16546
|
}
|
|
16441
16547
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
16442
16548
|
const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
|
|
@@ -16492,7 +16598,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16492
16598
|
url.searchParams.delete("mollie_payment_id");
|
|
16493
16599
|
url.searchParams.delete("mollie_status");
|
|
16494
16600
|
window.history.replaceState({}, "", url.toString());
|
|
16495
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId }), jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
|
|
16601
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: isVoucherFormOpen, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: preselectedVoucherEventTypeId, voucherPurchaseResult: voucherPurchaseResult, isSuccess: isSuccess, showStandaloneCard: false, onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => {
|
|
16496
16602
|
setIsSuccess(false);
|
|
16497
16603
|
setVoucherPurchaseResult(null);
|
|
16498
16604
|
const url = new URL(window.location.href);
|
|
@@ -16507,11 +16613,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16507
16613
|
function UniversalBookingWidget(props) {
|
|
16508
16614
|
const [languagePolicy, setLanguagePolicy] = useState(null);
|
|
16509
16615
|
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 } : {};
|
|
16616
|
+
const providerProps = props.config.locale ? { configLocale: props.config.locale } : {};
|
|
16515
16617
|
const showLanguagePicker = props.config.showLanguagePicker !== false &&
|
|
16516
16618
|
(languagePolicy === null || languagePolicy.multiLanguageEnabled);
|
|
16517
16619
|
return (jsx(I18nProvider, { ...providerProps, children: jsx(ShowLanguagePickerProvider, { value: showLanguagePicker, children: jsx(TimezoneProvider, { value: orgTimezone, children: jsx(UniversalBookingWidgetInner, { ...props, onWidgetLanguage: setLanguagePolicy, onTimezone: setOrgTimezone }) }) }) }));
|