@bigz-app/booking-widget 1.3.1 → 1.3.3
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 +230 -83
- 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/BookingSuccessModal.d.ts +3 -1
- package/dist/components/booking/BookingSuccessModal.d.ts.map +1 -1
- package/dist/index.cjs +230 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +230 -83
- package/dist/index.esm.js.map +1 -1
- package/dist/utils/analytics.d.ts +17 -0
- package/dist/utils/analytics.d.ts.map +1 -0
- package/dist/utils/google-ads-tracking.d.ts +9 -19
- package/dist/utils/google-ads-tracking.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -12028,121 +12028,145 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
12028
12028
|
}
|
|
12029
12029
|
|
|
12030
12030
|
/**
|
|
12031
|
-
* Google Ads
|
|
12031
|
+
* Google Ads Tracking Utility
|
|
12032
12032
|
*
|
|
12033
|
-
*
|
|
12034
|
-
|
|
12035
|
-
/**
|
|
12036
|
-
* 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.
|
|
12037
12035
|
*/
|
|
12038
12036
|
function isGtagAvailable() {
|
|
12039
|
-
if (typeof window === "undefined")
|
|
12037
|
+
if (typeof window === "undefined")
|
|
12040
12038
|
return false;
|
|
12041
|
-
|
|
12042
|
-
// Check current window
|
|
12043
|
-
if (typeof window.gtag === "function") {
|
|
12039
|
+
if (typeof window.gtag === "function")
|
|
12044
12040
|
return true;
|
|
12045
|
-
}
|
|
12046
|
-
// Check parent window (for iframe/widget scenarios)
|
|
12047
12041
|
if (window !== window.parent) {
|
|
12048
12042
|
try {
|
|
12049
|
-
if (typeof window.parent?.gtag === "function")
|
|
12043
|
+
if (typeof window.parent?.gtag === "function")
|
|
12050
12044
|
return true;
|
|
12051
|
-
}
|
|
12052
12045
|
}
|
|
12053
12046
|
catch {
|
|
12054
|
-
//
|
|
12047
|
+
// Cross-origin
|
|
12055
12048
|
}
|
|
12056
12049
|
}
|
|
12057
12050
|
return false;
|
|
12058
12051
|
}
|
|
12059
|
-
/**
|
|
12060
|
-
* Initialize gtag if not already available
|
|
12061
|
-
*/
|
|
12062
12052
|
function initializeGtag(tagId) {
|
|
12063
|
-
if (typeof window === "undefined")
|
|
12053
|
+
if (typeof window === "undefined" || isGtagAvailable())
|
|
12064
12054
|
return;
|
|
12065
|
-
}
|
|
12066
|
-
// Skip if gtag already exists
|
|
12067
|
-
if (isGtagAvailable()) {
|
|
12068
|
-
return;
|
|
12069
|
-
}
|
|
12070
|
-
// Initialize dataLayer and gtag function
|
|
12071
12055
|
window.dataLayer = window.dataLayer || [];
|
|
12072
12056
|
window.gtag = (...args) => {
|
|
12073
12057
|
window.dataLayer.push(args);
|
|
12074
12058
|
};
|
|
12075
|
-
// Set current timestamp
|
|
12076
12059
|
window.gtag("js", new Date());
|
|
12077
|
-
// Load gtag script
|
|
12078
12060
|
const script = document.createElement("script");
|
|
12079
12061
|
script.async = true;
|
|
12080
12062
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${tagId}`;
|
|
12081
12063
|
document.head.appendChild(script);
|
|
12082
|
-
// Configure the tag
|
|
12083
12064
|
window.gtag("config", tagId, {
|
|
12084
12065
|
anonymize_ip: true,
|
|
12085
12066
|
allow_google_signals: false,
|
|
12086
12067
|
allow_ad_personalization_signals: false,
|
|
12087
12068
|
});
|
|
12088
12069
|
}
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
return;
|
|
12070
|
+
function getGtag() {
|
|
12071
|
+
if (typeof window === "undefined")
|
|
12072
|
+
return null;
|
|
12073
|
+
if (typeof window.gtag === "function") {
|
|
12074
|
+
return window.gtag;
|
|
12095
12075
|
}
|
|
12096
|
-
|
|
12097
|
-
// Try parent window gtag if current window doesn't have it
|
|
12098
|
-
if (typeof gtag !== "function" && window !== window.parent) {
|
|
12076
|
+
if (window !== window.parent) {
|
|
12099
12077
|
try {
|
|
12100
|
-
|
|
12078
|
+
const parentGtag = window.parent?.gtag;
|
|
12079
|
+
if (typeof parentGtag === "function")
|
|
12080
|
+
return parentGtag;
|
|
12101
12081
|
}
|
|
12102
12082
|
catch {
|
|
12103
|
-
//
|
|
12083
|
+
// Cross-origin
|
|
12104
12084
|
}
|
|
12105
12085
|
}
|
|
12106
|
-
|
|
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")
|
|
12107
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);
|
|
12108
12105
|
}
|
|
12109
|
-
|
|
12110
|
-
|
|
12106
|
+
}
|
|
12107
|
+
function sendConversion(config) {
|
|
12108
|
+
const params = {
|
|
12111
12109
|
send_to: `${config.tagId}/${config.conversionId}`,
|
|
12112
12110
|
};
|
|
12113
|
-
// Add optional parameters
|
|
12114
12111
|
if (config.conversionValue !== undefined) {
|
|
12115
|
-
|
|
12112
|
+
params.value = config.conversionValue;
|
|
12116
12113
|
}
|
|
12117
12114
|
if (config.conversionCurrency) {
|
|
12118
|
-
|
|
12115
|
+
params.currency = config.conversionCurrency;
|
|
12119
12116
|
}
|
|
12120
12117
|
if (config.transactionId) {
|
|
12121
|
-
|
|
12118
|
+
params.transaction_id = config.transactionId;
|
|
12122
12119
|
}
|
|
12123
|
-
|
|
12124
|
-
gtag("event", "conversion", conversionData);
|
|
12120
|
+
sendEvent("conversion", params);
|
|
12125
12121
|
}
|
|
12126
12122
|
/**
|
|
12127
|
-
*
|
|
12128
|
-
* Waits 1500ms, checks/initializes gtag, then sends conversion
|
|
12123
|
+
* Track widget pageview (fired once on widget mount).
|
|
12129
12124
|
*/
|
|
12130
|
-
function
|
|
12131
|
-
|
|
12132
|
-
if (!config.tagId || !config.conversionId) {
|
|
12125
|
+
function handleGoogleAdsPageview(tagId, consent) {
|
|
12126
|
+
if (!tagId || false || typeof window === "undefined")
|
|
12133
12127
|
return;
|
|
12128
|
+
if (!isGtagAvailable()) {
|
|
12129
|
+
initializeGtag(tagId);
|
|
12134
12130
|
}
|
|
12135
|
-
|
|
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;
|
|
12136
12152
|
setTimeout(() => {
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
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);
|
|
12140
12165
|
}
|
|
12141
|
-
sendConversion(config);
|
|
12142
12166
|
}, 1500);
|
|
12143
12167
|
}
|
|
12144
12168
|
|
|
12145
|
-
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, }) => {
|
|
12169
|
+
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, googleAdsConfig: googleAdsConfigProp, }) => {
|
|
12146
12170
|
const t = useTranslations();
|
|
12147
12171
|
const { locale } = useLocale();
|
|
12148
12172
|
const timezone = useTimezone();
|
|
@@ -12191,20 +12215,16 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12191
12215
|
});
|
|
12192
12216
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
12193
12217
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
12218
|
+
const adsConfig = googleAdsConfigProp ?? data.googleAdsConfig;
|
|
12194
12219
|
if (finalPaymentStatus === "succeeded" &&
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
config.googleAds?.consent !== false) {
|
|
12198
|
-
// Prepare conversion tracking data
|
|
12199
|
-
const conversionValue = data.order.total / 100;
|
|
12200
|
-
const transactionId = data.order.id;
|
|
12201
|
-
// Track the conversion
|
|
12220
|
+
adsConfig?.tagId &&
|
|
12221
|
+
adsConfig?.conversionId) {
|
|
12202
12222
|
handleGoogleAdsConversion({
|
|
12203
|
-
tagId:
|
|
12204
|
-
conversionId:
|
|
12205
|
-
conversionValue,
|
|
12206
|
-
conversionCurrency:
|
|
12207
|
-
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,
|
|
12208
12228
|
});
|
|
12209
12229
|
}
|
|
12210
12230
|
}
|
|
@@ -15390,6 +15410,88 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
15390
15410
|
return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsxRuntime.jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsxRuntime.jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsxRuntime.jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: "16px", paddingBottom: "16px", paddingTop: "16px", borderTop: "1px solid var(--bw-border-color)", fontSize: "14px" }, children: [jsxRuntime.jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
|
|
15391
15411
|
}
|
|
15392
15412
|
|
|
15413
|
+
/**
|
|
15414
|
+
* Widget analytics — server-side PostHog tracking via batched sendBeacon.
|
|
15415
|
+
*
|
|
15416
|
+
* Events are buffered locally and flushed every few seconds (or on page
|
|
15417
|
+
* unload) as a single POST to `/api/booking/track`. This avoids extra
|
|
15418
|
+
* network requests on every click while still capturing a complete funnel.
|
|
15419
|
+
*/
|
|
15420
|
+
let buffer = [];
|
|
15421
|
+
let flushTimer = null;
|
|
15422
|
+
let currentApiBaseUrl = "";
|
|
15423
|
+
let currentOrganizationId = "";
|
|
15424
|
+
const FLUSH_INTERVAL_MS = 3000;
|
|
15425
|
+
function flush() {
|
|
15426
|
+
if (buffer.length === 0)
|
|
15427
|
+
return;
|
|
15428
|
+
const payload = JSON.stringify({
|
|
15429
|
+
organizationId: currentOrganizationId,
|
|
15430
|
+
events: buffer,
|
|
15431
|
+
});
|
|
15432
|
+
buffer = [];
|
|
15433
|
+
const url = getApiUrl(currentApiBaseUrl, "/booking/track");
|
|
15434
|
+
// Use fetch with keepalive as primary — works cross-origin with CORS.
|
|
15435
|
+
// sendBeacon silently fails cross-origin with application/json because
|
|
15436
|
+
// it can't do CORS preflight, so we only use it as a text/plain fallback
|
|
15437
|
+
// on page unload when fetch may be aborted.
|
|
15438
|
+
try {
|
|
15439
|
+
void fetch(url, {
|
|
15440
|
+
method: "POST",
|
|
15441
|
+
headers: { "Content-Type": "application/json" },
|
|
15442
|
+
body: payload,
|
|
15443
|
+
keepalive: true,
|
|
15444
|
+
});
|
|
15445
|
+
}
|
|
15446
|
+
catch {
|
|
15447
|
+
// fetch failed (e.g. during page unload) — try sendBeacon with text/plain
|
|
15448
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
15449
|
+
const blob = new Blob([payload], { type: "text/plain" });
|
|
15450
|
+
navigator.sendBeacon(url, blob);
|
|
15451
|
+
}
|
|
15452
|
+
}
|
|
15453
|
+
}
|
|
15454
|
+
function scheduleFlush() {
|
|
15455
|
+
if (flushTimer)
|
|
15456
|
+
return;
|
|
15457
|
+
flushTimer = setTimeout(() => {
|
|
15458
|
+
flushTimer = null;
|
|
15459
|
+
flush();
|
|
15460
|
+
}, FLUSH_INTERVAL_MS);
|
|
15461
|
+
}
|
|
15462
|
+
/**
|
|
15463
|
+
* Initialise the analytics module. Must be called once before `trackEvent`.
|
|
15464
|
+
*/
|
|
15465
|
+
function initAnalytics(apiBaseUrl, organizationId) {
|
|
15466
|
+
currentApiBaseUrl = apiBaseUrl;
|
|
15467
|
+
currentOrganizationId = organizationId;
|
|
15468
|
+
if (typeof window !== "undefined") {
|
|
15469
|
+
window.addEventListener("pagehide", flush);
|
|
15470
|
+
window.addEventListener("visibilitychange", () => {
|
|
15471
|
+
if (document.visibilityState === "hidden")
|
|
15472
|
+
flush();
|
|
15473
|
+
});
|
|
15474
|
+
}
|
|
15475
|
+
}
|
|
15476
|
+
/**
|
|
15477
|
+
* Queue a widget event. Non-blocking, fire-and-forget.
|
|
15478
|
+
*/
|
|
15479
|
+
function trackEvent(event, properties = {}) {
|
|
15480
|
+
if (typeof window === "undefined")
|
|
15481
|
+
return;
|
|
15482
|
+
if (!currentOrganizationId)
|
|
15483
|
+
return;
|
|
15484
|
+
buffer.push({
|
|
15485
|
+
event,
|
|
15486
|
+
properties,
|
|
15487
|
+
domain: window.location.hostname,
|
|
15488
|
+
url: window.location.href,
|
|
15489
|
+
referrer: document.referrer,
|
|
15490
|
+
timestamp: new Date().toISOString(),
|
|
15491
|
+
});
|
|
15492
|
+
scheduleFlush();
|
|
15493
|
+
}
|
|
15494
|
+
|
|
15393
15495
|
// Main widget component
|
|
15394
15496
|
function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onTimezone, }) {
|
|
15395
15497
|
const t = useTranslations();
|
|
@@ -15469,6 +15571,13 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15469
15571
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = React.useState(false);
|
|
15470
15572
|
const [shouldRenderUpsells, setShouldRenderUpsells] = React.useState(false);
|
|
15471
15573
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = React.useState(false);
|
|
15574
|
+
// Google Ads config (received from API, set once from the first API response)
|
|
15575
|
+
const [googleAdsConfig, setGoogleAdsConfig] = React.useState(null);
|
|
15576
|
+
const extractGoogleAdsConfig = (data) => {
|
|
15577
|
+
if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
|
|
15578
|
+
setGoogleAdsConfig(data.googleAdsConfig);
|
|
15579
|
+
}
|
|
15580
|
+
};
|
|
15472
15581
|
// Promo dialog state
|
|
15473
15582
|
const [showPromoDialog, setShowPromoDialog] = React.useState(false);
|
|
15474
15583
|
const [widgetContainerRef, setWidgetContainerRef] = React.useState(null);
|
|
@@ -15552,6 +15661,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15552
15661
|
image: resolvedImage,
|
|
15553
15662
|
};
|
|
15554
15663
|
setVoucherConfig(mergedConfig);
|
|
15664
|
+
extractGoogleAdsConfig(data);
|
|
15555
15665
|
setVoucherEventTypes(data.eventTypes || []);
|
|
15556
15666
|
// Set system config for payment processing
|
|
15557
15667
|
if (data.paymentProvider) {
|
|
@@ -15574,6 +15684,29 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15574
15684
|
setIsLoadingVoucherConfig(false);
|
|
15575
15685
|
}
|
|
15576
15686
|
};
|
|
15687
|
+
// Initialise analytics once on mount
|
|
15688
|
+
const analyticsInitRef = React.useRef(false);
|
|
15689
|
+
React.useEffect(() => {
|
|
15690
|
+
if (!analyticsInitRef.current && config.organizationId) {
|
|
15691
|
+
analyticsInitRef.current = true;
|
|
15692
|
+
initAnalytics(config.apiBaseUrl, config.organizationId);
|
|
15693
|
+
trackEvent("widget_loaded", {
|
|
15694
|
+
viewMode,
|
|
15695
|
+
eventTypeId: config.eventTypeId,
|
|
15696
|
+
categoryId: config.categoryId,
|
|
15697
|
+
eventInstanceId: config.eventInstanceId,
|
|
15698
|
+
isStandaloneVoucherMode,
|
|
15699
|
+
});
|
|
15700
|
+
}
|
|
15701
|
+
}, [config.organizationId, config.apiBaseUrl]);
|
|
15702
|
+
// Fire widget pageview once when Google Ads config is received from API
|
|
15703
|
+
const pageviewFiredRef = React.useRef(false);
|
|
15704
|
+
React.useEffect(() => {
|
|
15705
|
+
if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
|
|
15706
|
+
pageviewFiredRef.current = true;
|
|
15707
|
+
handleGoogleAdsPageview(googleAdsConfig.tagId);
|
|
15708
|
+
}
|
|
15709
|
+
}, [googleAdsConfig]);
|
|
15577
15710
|
// Determine initial step and load data
|
|
15578
15711
|
React.useEffect(() => {
|
|
15579
15712
|
const initializeWidget = async () => {
|
|
@@ -15662,6 +15795,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15662
15795
|
});
|
|
15663
15796
|
const voucherData = await voucherResponse.json();
|
|
15664
15797
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15798
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "stripe_redirect" });
|
|
15665
15799
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15666
15800
|
setIsSuccess(true);
|
|
15667
15801
|
setSuccessPaymentId(null);
|
|
@@ -15671,6 +15805,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15671
15805
|
catch {
|
|
15672
15806
|
// Fall back to booking success flow if voucher lookup fails.
|
|
15673
15807
|
}
|
|
15808
|
+
trackEvent("booking_completed", { paymentIntentId: stripeReturn.paymentIntent, source: "stripe_redirect" });
|
|
15674
15809
|
setVoucherPurchaseResult(null);
|
|
15675
15810
|
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
15676
15811
|
setIsSuccess(true);
|
|
@@ -15708,6 +15843,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15708
15843
|
});
|
|
15709
15844
|
const voucherData = await voucherResponse.json();
|
|
15710
15845
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15846
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "mollie_redirect" });
|
|
15711
15847
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15712
15848
|
setIsSuccess(true);
|
|
15713
15849
|
setSuccessPaymentId(null);
|
|
@@ -15746,6 +15882,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15746
15882
|
window[globalFlagKey] = true;
|
|
15747
15883
|
const timer = setTimeout(() => {
|
|
15748
15884
|
setShowPromoDialog(true);
|
|
15885
|
+
trackEvent("promo_dialog_shown", { discountCode: config.promo?.discountCode });
|
|
15749
15886
|
}, 1000);
|
|
15750
15887
|
return () => clearTimeout(timer);
|
|
15751
15888
|
}, [config.promo?.enabled, config.promo?.discountCode]);
|
|
@@ -15755,6 +15892,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15755
15892
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
15756
15893
|
};
|
|
15757
15894
|
const handlePromoCtaClick = () => {
|
|
15895
|
+
trackEvent("promo_cta_clicked", { discountCode: config.promo?.discountCode });
|
|
15758
15896
|
setShowPromoDialog(false);
|
|
15759
15897
|
const promoId = config.promo?.discountCode || "default";
|
|
15760
15898
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
@@ -15787,7 +15925,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15787
15925
|
onWidgetLanguage?.(wl);
|
|
15788
15926
|
onTimezone?.(wl.timezone);
|
|
15789
15927
|
}
|
|
15928
|
+
extractGoogleAdsConfig(data);
|
|
15790
15929
|
setEventTypes(data.eventTypes);
|
|
15930
|
+
trackEvent("event_types_loaded", { count: data.eventTypes.length });
|
|
15791
15931
|
if (isSingleEventTypeMode && data.eventTypes.length === 1) {
|
|
15792
15932
|
setSelectedEventType(data.eventTypes[0]);
|
|
15793
15933
|
await loadEventInstances(data.eventTypes[0].id);
|
|
@@ -15823,6 +15963,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15823
15963
|
onWidgetLanguage?.(wl);
|
|
15824
15964
|
onTimezone?.(wl.timezone);
|
|
15825
15965
|
}
|
|
15966
|
+
extractGoogleAdsConfig(data);
|
|
15826
15967
|
setUpcomingEvents(data.upcomingEvents || []);
|
|
15827
15968
|
}
|
|
15828
15969
|
else {
|
|
@@ -15858,6 +15999,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15858
15999
|
onWidgetLanguage?.(wl);
|
|
15859
16000
|
onTimezone?.(wl.timezone);
|
|
15860
16001
|
}
|
|
16002
|
+
extractGoogleAdsConfig(data);
|
|
15861
16003
|
setSpecials(data.specials || []);
|
|
15862
16004
|
}
|
|
15863
16005
|
else {
|
|
@@ -15884,6 +16026,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15884
16026
|
onWidgetLanguage?.(wl);
|
|
15885
16027
|
onTimezone?.(wl.timezone);
|
|
15886
16028
|
}
|
|
16029
|
+
extractGoogleAdsConfig(data);
|
|
15887
16030
|
setEventInstances(data.eventInstances);
|
|
15888
16031
|
if (data.paymentProvider) {
|
|
15889
16032
|
setSystemConfig({
|
|
@@ -15944,6 +16087,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15944
16087
|
onWidgetLanguage?.(wl);
|
|
15945
16088
|
onTimezone?.(wl.timezone);
|
|
15946
16089
|
}
|
|
16090
|
+
extractGoogleAdsConfig(data);
|
|
15947
16091
|
setEventDetails(data.eventDetails);
|
|
15948
16092
|
setSystemConfig({
|
|
15949
16093
|
paymentProvider: data.paymentProvider,
|
|
@@ -16013,6 +16157,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16013
16157
|
onWidgetLanguage?.(wl);
|
|
16014
16158
|
onTimezone?.(wl.timezone);
|
|
16015
16159
|
}
|
|
16160
|
+
extractGoogleAdsConfig(data);
|
|
16016
16161
|
return data.upsells || [];
|
|
16017
16162
|
}
|
|
16018
16163
|
else {
|
|
@@ -16038,6 +16183,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16038
16183
|
}
|
|
16039
16184
|
// Event type selection handlers
|
|
16040
16185
|
const handleEventTypeSelect = async (eventType) => {
|
|
16186
|
+
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16041
16187
|
setSelectedEventType(eventType);
|
|
16042
16188
|
setCurrentStep("eventInstances");
|
|
16043
16189
|
setShouldRenderInstanceSelection(true);
|
|
@@ -16051,6 +16197,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16051
16197
|
};
|
|
16052
16198
|
// Event instance selection handlers
|
|
16053
16199
|
const handleEventInstanceSelect = async (eventInstance) => {
|
|
16200
|
+
trackEvent("event_instance_selected", { eventInstanceId: eventInstance.id, eventInstanceName: eventInstance.name });
|
|
16054
16201
|
setSelectedEventInstance(eventInstance);
|
|
16055
16202
|
bookingReturnStep.current = "eventInstances";
|
|
16056
16203
|
// Set default participant count for upsell calculations
|
|
@@ -16063,9 +16210,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16063
16210
|
try {
|
|
16064
16211
|
const availableUpsells = await loadUpsells(selectedEventType.id, eventInstance.id, defaultParticipantCount);
|
|
16065
16212
|
if (availableUpsells.length > 0) {
|
|
16066
|
-
|
|
16213
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16067
16214
|
setUpsells(availableUpsells);
|
|
16068
|
-
// Pre-select default-checked upsells
|
|
16069
16215
|
const defaultSelections = availableUpsells
|
|
16070
16216
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16071
16217
|
.map((upsell) => ({
|
|
@@ -16075,7 +16221,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16075
16221
|
setSelectedUpsells(defaultSelections);
|
|
16076
16222
|
setCurrentStep("upsells");
|
|
16077
16223
|
setIsLoadingUpsells(false);
|
|
16078
|
-
return;
|
|
16224
|
+
return;
|
|
16079
16225
|
}
|
|
16080
16226
|
}
|
|
16081
16227
|
catch (err) {
|
|
@@ -16085,7 +16231,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16085
16231
|
setIsLoadingUpsells(false);
|
|
16086
16232
|
}
|
|
16087
16233
|
}
|
|
16088
|
-
|
|
16234
|
+
trackEvent("booking_form_opened", { fromUpsells: false });
|
|
16089
16235
|
setCurrentStep("booking");
|
|
16090
16236
|
setShouldRenderBookingForm(true);
|
|
16091
16237
|
setIsLoadingEventDetails(true);
|
|
@@ -16108,6 +16254,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16108
16254
|
setEventDetails(null);
|
|
16109
16255
|
};
|
|
16110
16256
|
const handleBookingSuccess = (result) => {
|
|
16257
|
+
trackEvent("booking_completed", { paymentIntentId: result.paymentIntent?.id });
|
|
16111
16258
|
setIsSuccess(true);
|
|
16112
16259
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16113
16260
|
setSidebarOpen(false);
|
|
@@ -16123,7 +16270,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16123
16270
|
setSelectedUpsells(selections);
|
|
16124
16271
|
};
|
|
16125
16272
|
const handleUpsellsContinue = async () => {
|
|
16126
|
-
|
|
16273
|
+
trackEvent("booking_form_opened", { fromUpsells: true });
|
|
16127
16274
|
setCurrentStep("booking");
|
|
16128
16275
|
setShouldRenderBookingForm(true);
|
|
16129
16276
|
setIsLoadingEventDetails(true);
|
|
@@ -16143,8 +16290,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16143
16290
|
};
|
|
16144
16291
|
// Voucher purchase handlers
|
|
16145
16292
|
const handleVoucherCardClick = async () => {
|
|
16293
|
+
trackEvent("voucher_card_clicked");
|
|
16146
16294
|
setPreselectedVoucherEventTypeId(null);
|
|
16147
|
-
// Ensure voucher config and event types are loaded before opening the form
|
|
16148
16295
|
if (!voucherConfig || voucherEventTypes.length === 0) {
|
|
16149
16296
|
await loadVoucherConfig();
|
|
16150
16297
|
}
|
|
@@ -16163,6 +16310,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16163
16310
|
setPreselectedVoucherEventTypeId(null);
|
|
16164
16311
|
};
|
|
16165
16312
|
const handleVoucherSuccess = (result) => {
|
|
16313
|
+
trackEvent("voucher_purchased", { voucherType: result.voucherType });
|
|
16166
16314
|
setVoucherPurchaseResult(result);
|
|
16167
16315
|
setIsVoucherFormOpen(false);
|
|
16168
16316
|
setIsSuccess(true);
|
|
@@ -16245,8 +16393,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16245
16393
|
try {
|
|
16246
16394
|
const availableUpsells = await loadUpsells(eventTypeForUpsells.id, eventInstanceId, defaultParticipantCount);
|
|
16247
16395
|
if (availableUpsells.length > 0) {
|
|
16396
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16248
16397
|
setUpsells(availableUpsells);
|
|
16249
|
-
// Pre-select default-checked upsells
|
|
16250
16398
|
const defaultSelections = availableUpsells
|
|
16251
16399
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16252
16400
|
.map((upsell) => ({
|
|
@@ -16256,7 +16404,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16256
16404
|
setSelectedUpsells(defaultSelections);
|
|
16257
16405
|
setCurrentStep("upsells");
|
|
16258
16406
|
setIsLoadingUpsells(false);
|
|
16259
|
-
// Load event details in background for when user continues past upsells
|
|
16260
16407
|
void loadEventDetails(eventInstanceId);
|
|
16261
16408
|
return;
|
|
16262
16409
|
}
|
|
@@ -16426,7 +16573,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16426
16573
|
url.searchParams.delete("mollie_payment_id");
|
|
16427
16574
|
url.searchParams.delete("mollie_status");
|
|
16428
16575
|
window.history.replaceState({}, "", url.toString());
|
|
16429
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16576
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16430
16577
|
}
|
|
16431
16578
|
if (viewMode === "specials" && showingPreview) {
|
|
16432
16579
|
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: () => {
|
|
@@ -16451,7 +16598,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16451
16598
|
setShouldRenderBookingForm(false);
|
|
16452
16599
|
setSelectedUpsells([]);
|
|
16453
16600
|
setUpsells([]);
|
|
16454
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16601
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16455
16602
|
}
|
|
16456
16603
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
16457
16604
|
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -16474,7 +16621,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16474
16621
|
url.searchParams.delete("mollie_payment_id");
|
|
16475
16622
|
url.searchParams.delete("mollie_status");
|
|
16476
16623
|
window.history.replaceState({}, "", url.toString());
|
|
16477
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16624
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16478
16625
|
}
|
|
16479
16626
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
16480
16627
|
return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -16520,7 +16667,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16520
16667
|
url.searchParams.delete("mollie_payment_id");
|
|
16521
16668
|
url.searchParams.delete("mollie_status");
|
|
16522
16669
|
window.history.replaceState({}, "", url.toString());
|
|
16523
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16670
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16524
16671
|
}
|
|
16525
16672
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
16526
16673
|
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: {
|
|
@@ -16576,7 +16723,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16576
16723
|
url.searchParams.delete("mollie_payment_id");
|
|
16577
16724
|
url.searchParams.delete("mollie_status");
|
|
16578
16725
|
window.history.replaceState({}, "", url.toString());
|
|
16579
|
-
}, 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: () => {
|
|
16726
|
+
}, 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: () => {
|
|
16580
16727
|
setIsSuccess(false);
|
|
16581
16728
|
setVoucherPurchaseResult(null);
|
|
16582
16729
|
const url = new URL(window.location.href);
|