@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.esm.js
CHANGED
|
@@ -12008,121 +12008,145 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
|
|
|
12008
12008
|
}
|
|
12009
12009
|
|
|
12010
12010
|
/**
|
|
12011
|
-
* Google Ads
|
|
12011
|
+
* Google Ads Tracking Utility
|
|
12012
12012
|
*
|
|
12013
|
-
*
|
|
12014
|
-
|
|
12015
|
-
/**
|
|
12016
|
-
* 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.
|
|
12017
12015
|
*/
|
|
12018
12016
|
function isGtagAvailable() {
|
|
12019
|
-
if (typeof window === "undefined")
|
|
12017
|
+
if (typeof window === "undefined")
|
|
12020
12018
|
return false;
|
|
12021
|
-
|
|
12022
|
-
// Check current window
|
|
12023
|
-
if (typeof window.gtag === "function") {
|
|
12019
|
+
if (typeof window.gtag === "function")
|
|
12024
12020
|
return true;
|
|
12025
|
-
}
|
|
12026
|
-
// Check parent window (for iframe/widget scenarios)
|
|
12027
12021
|
if (window !== window.parent) {
|
|
12028
12022
|
try {
|
|
12029
|
-
if (typeof window.parent?.gtag === "function")
|
|
12023
|
+
if (typeof window.parent?.gtag === "function")
|
|
12030
12024
|
return true;
|
|
12031
|
-
}
|
|
12032
12025
|
}
|
|
12033
12026
|
catch {
|
|
12034
|
-
//
|
|
12027
|
+
// Cross-origin
|
|
12035
12028
|
}
|
|
12036
12029
|
}
|
|
12037
12030
|
return false;
|
|
12038
12031
|
}
|
|
12039
|
-
/**
|
|
12040
|
-
* Initialize gtag if not already available
|
|
12041
|
-
*/
|
|
12042
12032
|
function initializeGtag(tagId) {
|
|
12043
|
-
if (typeof window === "undefined")
|
|
12033
|
+
if (typeof window === "undefined" || isGtagAvailable())
|
|
12044
12034
|
return;
|
|
12045
|
-
}
|
|
12046
|
-
// Skip if gtag already exists
|
|
12047
|
-
if (isGtagAvailable()) {
|
|
12048
|
-
return;
|
|
12049
|
-
}
|
|
12050
|
-
// Initialize dataLayer and gtag function
|
|
12051
12035
|
window.dataLayer = window.dataLayer || [];
|
|
12052
12036
|
window.gtag = (...args) => {
|
|
12053
12037
|
window.dataLayer.push(args);
|
|
12054
12038
|
};
|
|
12055
|
-
// Set current timestamp
|
|
12056
12039
|
window.gtag("js", new Date());
|
|
12057
|
-
// Load gtag script
|
|
12058
12040
|
const script = document.createElement("script");
|
|
12059
12041
|
script.async = true;
|
|
12060
12042
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${tagId}`;
|
|
12061
12043
|
document.head.appendChild(script);
|
|
12062
|
-
// Configure the tag
|
|
12063
12044
|
window.gtag("config", tagId, {
|
|
12064
12045
|
anonymize_ip: true,
|
|
12065
12046
|
allow_google_signals: false,
|
|
12066
12047
|
allow_ad_personalization_signals: false,
|
|
12067
12048
|
});
|
|
12068
12049
|
}
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
return;
|
|
12050
|
+
function getGtag() {
|
|
12051
|
+
if (typeof window === "undefined")
|
|
12052
|
+
return null;
|
|
12053
|
+
if (typeof window.gtag === "function") {
|
|
12054
|
+
return window.gtag;
|
|
12075
12055
|
}
|
|
12076
|
-
|
|
12077
|
-
// Try parent window gtag if current window doesn't have it
|
|
12078
|
-
if (typeof gtag !== "function" && window !== window.parent) {
|
|
12056
|
+
if (window !== window.parent) {
|
|
12079
12057
|
try {
|
|
12080
|
-
|
|
12058
|
+
const parentGtag = window.parent?.gtag;
|
|
12059
|
+
if (typeof parentGtag === "function")
|
|
12060
|
+
return parentGtag;
|
|
12081
12061
|
}
|
|
12082
12062
|
catch {
|
|
12083
|
-
//
|
|
12063
|
+
// Cross-origin
|
|
12084
12064
|
}
|
|
12085
12065
|
}
|
|
12086
|
-
|
|
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")
|
|
12087
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);
|
|
12088
12085
|
}
|
|
12089
|
-
|
|
12090
|
-
|
|
12086
|
+
}
|
|
12087
|
+
function sendConversion(config) {
|
|
12088
|
+
const params = {
|
|
12091
12089
|
send_to: `${config.tagId}/${config.conversionId}`,
|
|
12092
12090
|
};
|
|
12093
|
-
// Add optional parameters
|
|
12094
12091
|
if (config.conversionValue !== undefined) {
|
|
12095
|
-
|
|
12092
|
+
params.value = config.conversionValue;
|
|
12096
12093
|
}
|
|
12097
12094
|
if (config.conversionCurrency) {
|
|
12098
|
-
|
|
12095
|
+
params.currency = config.conversionCurrency;
|
|
12099
12096
|
}
|
|
12100
12097
|
if (config.transactionId) {
|
|
12101
|
-
|
|
12098
|
+
params.transaction_id = config.transactionId;
|
|
12102
12099
|
}
|
|
12103
|
-
|
|
12104
|
-
gtag("event", "conversion", conversionData);
|
|
12100
|
+
sendEvent("conversion", params);
|
|
12105
12101
|
}
|
|
12106
12102
|
/**
|
|
12107
|
-
*
|
|
12108
|
-
* Waits 1500ms, checks/initializes gtag, then sends conversion
|
|
12103
|
+
* Track widget pageview (fired once on widget mount).
|
|
12109
12104
|
*/
|
|
12110
|
-
function
|
|
12111
|
-
|
|
12112
|
-
if (!config.tagId || !config.conversionId) {
|
|
12105
|
+
function handleGoogleAdsPageview(tagId, consent) {
|
|
12106
|
+
if (!tagId || false || typeof window === "undefined")
|
|
12113
12107
|
return;
|
|
12108
|
+
if (!isGtagAvailable()) {
|
|
12109
|
+
initializeGtag(tagId);
|
|
12114
12110
|
}
|
|
12115
|
-
|
|
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;
|
|
12116
12132
|
setTimeout(() => {
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
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);
|
|
12120
12145
|
}
|
|
12121
|
-
sendConversion(config);
|
|
12122
12146
|
}, 1500);
|
|
12123
12147
|
}
|
|
12124
12148
|
|
|
12125
|
-
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, }) => {
|
|
12149
|
+
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, googleAdsConfig: googleAdsConfigProp, }) => {
|
|
12126
12150
|
const t = useTranslations();
|
|
12127
12151
|
const { locale } = useLocale();
|
|
12128
12152
|
const timezone = useTimezone();
|
|
@@ -12171,20 +12195,16 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
|
|
|
12171
12195
|
});
|
|
12172
12196
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
12173
12197
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
12198
|
+
const adsConfig = googleAdsConfigProp ?? data.googleAdsConfig;
|
|
12174
12199
|
if (finalPaymentStatus === "succeeded" &&
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
config.googleAds?.consent !== false) {
|
|
12178
|
-
// Prepare conversion tracking data
|
|
12179
|
-
const conversionValue = data.order.total / 100;
|
|
12180
|
-
const transactionId = data.order.id;
|
|
12181
|
-
// Track the conversion
|
|
12200
|
+
adsConfig?.tagId &&
|
|
12201
|
+
adsConfig?.conversionId) {
|
|
12182
12202
|
handleGoogleAdsConversion({
|
|
12183
|
-
tagId:
|
|
12184
|
-
conversionId:
|
|
12185
|
-
conversionValue,
|
|
12186
|
-
conversionCurrency:
|
|
12187
|
-
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,
|
|
12188
12208
|
});
|
|
12189
12209
|
}
|
|
12190
12210
|
}
|
|
@@ -15370,6 +15390,88 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
|
|
|
15370
15390
|
return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (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: [jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
|
|
15371
15391
|
}
|
|
15372
15392
|
|
|
15393
|
+
/**
|
|
15394
|
+
* Widget analytics — server-side PostHog tracking via batched sendBeacon.
|
|
15395
|
+
*
|
|
15396
|
+
* Events are buffered locally and flushed every few seconds (or on page
|
|
15397
|
+
* unload) as a single POST to `/api/booking/track`. This avoids extra
|
|
15398
|
+
* network requests on every click while still capturing a complete funnel.
|
|
15399
|
+
*/
|
|
15400
|
+
let buffer = [];
|
|
15401
|
+
let flushTimer = null;
|
|
15402
|
+
let currentApiBaseUrl = "";
|
|
15403
|
+
let currentOrganizationId = "";
|
|
15404
|
+
const FLUSH_INTERVAL_MS = 3000;
|
|
15405
|
+
function flush() {
|
|
15406
|
+
if (buffer.length === 0)
|
|
15407
|
+
return;
|
|
15408
|
+
const payload = JSON.stringify({
|
|
15409
|
+
organizationId: currentOrganizationId,
|
|
15410
|
+
events: buffer,
|
|
15411
|
+
});
|
|
15412
|
+
buffer = [];
|
|
15413
|
+
const url = getApiUrl(currentApiBaseUrl, "/booking/track");
|
|
15414
|
+
// Use fetch with keepalive as primary — works cross-origin with CORS.
|
|
15415
|
+
// sendBeacon silently fails cross-origin with application/json because
|
|
15416
|
+
// it can't do CORS preflight, so we only use it as a text/plain fallback
|
|
15417
|
+
// on page unload when fetch may be aborted.
|
|
15418
|
+
try {
|
|
15419
|
+
void fetch(url, {
|
|
15420
|
+
method: "POST",
|
|
15421
|
+
headers: { "Content-Type": "application/json" },
|
|
15422
|
+
body: payload,
|
|
15423
|
+
keepalive: true,
|
|
15424
|
+
});
|
|
15425
|
+
}
|
|
15426
|
+
catch {
|
|
15427
|
+
// fetch failed (e.g. during page unload) — try sendBeacon with text/plain
|
|
15428
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
15429
|
+
const blob = new Blob([payload], { type: "text/plain" });
|
|
15430
|
+
navigator.sendBeacon(url, blob);
|
|
15431
|
+
}
|
|
15432
|
+
}
|
|
15433
|
+
}
|
|
15434
|
+
function scheduleFlush() {
|
|
15435
|
+
if (flushTimer)
|
|
15436
|
+
return;
|
|
15437
|
+
flushTimer = setTimeout(() => {
|
|
15438
|
+
flushTimer = null;
|
|
15439
|
+
flush();
|
|
15440
|
+
}, FLUSH_INTERVAL_MS);
|
|
15441
|
+
}
|
|
15442
|
+
/**
|
|
15443
|
+
* Initialise the analytics module. Must be called once before `trackEvent`.
|
|
15444
|
+
*/
|
|
15445
|
+
function initAnalytics(apiBaseUrl, organizationId) {
|
|
15446
|
+
currentApiBaseUrl = apiBaseUrl;
|
|
15447
|
+
currentOrganizationId = organizationId;
|
|
15448
|
+
if (typeof window !== "undefined") {
|
|
15449
|
+
window.addEventListener("pagehide", flush);
|
|
15450
|
+
window.addEventListener("visibilitychange", () => {
|
|
15451
|
+
if (document.visibilityState === "hidden")
|
|
15452
|
+
flush();
|
|
15453
|
+
});
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
/**
|
|
15457
|
+
* Queue a widget event. Non-blocking, fire-and-forget.
|
|
15458
|
+
*/
|
|
15459
|
+
function trackEvent(event, properties = {}) {
|
|
15460
|
+
if (typeof window === "undefined")
|
|
15461
|
+
return;
|
|
15462
|
+
if (!currentOrganizationId)
|
|
15463
|
+
return;
|
|
15464
|
+
buffer.push({
|
|
15465
|
+
event,
|
|
15466
|
+
properties,
|
|
15467
|
+
domain: window.location.hostname,
|
|
15468
|
+
url: window.location.href,
|
|
15469
|
+
referrer: document.referrer,
|
|
15470
|
+
timestamp: new Date().toISOString(),
|
|
15471
|
+
});
|
|
15472
|
+
scheduleFlush();
|
|
15473
|
+
}
|
|
15474
|
+
|
|
15373
15475
|
// Main widget component
|
|
15374
15476
|
function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onTimezone, }) {
|
|
15375
15477
|
const t = useTranslations();
|
|
@@ -15449,6 +15551,13 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15449
15551
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
|
|
15450
15552
|
const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
|
|
15451
15553
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
|
|
15554
|
+
// Google Ads config (received from API, set once from the first API response)
|
|
15555
|
+
const [googleAdsConfig, setGoogleAdsConfig] = useState(null);
|
|
15556
|
+
const extractGoogleAdsConfig = (data) => {
|
|
15557
|
+
if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
|
|
15558
|
+
setGoogleAdsConfig(data.googleAdsConfig);
|
|
15559
|
+
}
|
|
15560
|
+
};
|
|
15452
15561
|
// Promo dialog state
|
|
15453
15562
|
const [showPromoDialog, setShowPromoDialog] = useState(false);
|
|
15454
15563
|
const [widgetContainerRef, setWidgetContainerRef] = useState(null);
|
|
@@ -15532,6 +15641,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15532
15641
|
image: resolvedImage,
|
|
15533
15642
|
};
|
|
15534
15643
|
setVoucherConfig(mergedConfig);
|
|
15644
|
+
extractGoogleAdsConfig(data);
|
|
15535
15645
|
setVoucherEventTypes(data.eventTypes || []);
|
|
15536
15646
|
// Set system config for payment processing
|
|
15537
15647
|
if (data.paymentProvider) {
|
|
@@ -15554,6 +15664,29 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15554
15664
|
setIsLoadingVoucherConfig(false);
|
|
15555
15665
|
}
|
|
15556
15666
|
};
|
|
15667
|
+
// Initialise analytics once on mount
|
|
15668
|
+
const analyticsInitRef = useRef(false);
|
|
15669
|
+
useEffect(() => {
|
|
15670
|
+
if (!analyticsInitRef.current && config.organizationId) {
|
|
15671
|
+
analyticsInitRef.current = true;
|
|
15672
|
+
initAnalytics(config.apiBaseUrl, config.organizationId);
|
|
15673
|
+
trackEvent("widget_loaded", {
|
|
15674
|
+
viewMode,
|
|
15675
|
+
eventTypeId: config.eventTypeId,
|
|
15676
|
+
categoryId: config.categoryId,
|
|
15677
|
+
eventInstanceId: config.eventInstanceId,
|
|
15678
|
+
isStandaloneVoucherMode,
|
|
15679
|
+
});
|
|
15680
|
+
}
|
|
15681
|
+
}, [config.organizationId, config.apiBaseUrl]);
|
|
15682
|
+
// Fire widget pageview once when Google Ads config is received from API
|
|
15683
|
+
const pageviewFiredRef = useRef(false);
|
|
15684
|
+
useEffect(() => {
|
|
15685
|
+
if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
|
|
15686
|
+
pageviewFiredRef.current = true;
|
|
15687
|
+
handleGoogleAdsPageview(googleAdsConfig.tagId);
|
|
15688
|
+
}
|
|
15689
|
+
}, [googleAdsConfig]);
|
|
15557
15690
|
// Determine initial step and load data
|
|
15558
15691
|
useEffect(() => {
|
|
15559
15692
|
const initializeWidget = async () => {
|
|
@@ -15642,6 +15775,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15642
15775
|
});
|
|
15643
15776
|
const voucherData = await voucherResponse.json();
|
|
15644
15777
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15778
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "stripe_redirect" });
|
|
15645
15779
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15646
15780
|
setIsSuccess(true);
|
|
15647
15781
|
setSuccessPaymentId(null);
|
|
@@ -15651,6 +15785,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15651
15785
|
catch {
|
|
15652
15786
|
// Fall back to booking success flow if voucher lookup fails.
|
|
15653
15787
|
}
|
|
15788
|
+
trackEvent("booking_completed", { paymentIntentId: stripeReturn.paymentIntent, source: "stripe_redirect" });
|
|
15654
15789
|
setVoucherPurchaseResult(null);
|
|
15655
15790
|
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
15656
15791
|
setIsSuccess(true);
|
|
@@ -15688,6 +15823,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15688
15823
|
});
|
|
15689
15824
|
const voucherData = await voucherResponse.json();
|
|
15690
15825
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15826
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "mollie_redirect" });
|
|
15691
15827
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15692
15828
|
setIsSuccess(true);
|
|
15693
15829
|
setSuccessPaymentId(null);
|
|
@@ -15726,6 +15862,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15726
15862
|
window[globalFlagKey] = true;
|
|
15727
15863
|
const timer = setTimeout(() => {
|
|
15728
15864
|
setShowPromoDialog(true);
|
|
15865
|
+
trackEvent("promo_dialog_shown", { discountCode: config.promo?.discountCode });
|
|
15729
15866
|
}, 1000);
|
|
15730
15867
|
return () => clearTimeout(timer);
|
|
15731
15868
|
}, [config.promo?.enabled, config.promo?.discountCode]);
|
|
@@ -15735,6 +15872,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15735
15872
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
15736
15873
|
};
|
|
15737
15874
|
const handlePromoCtaClick = () => {
|
|
15875
|
+
trackEvent("promo_cta_clicked", { discountCode: config.promo?.discountCode });
|
|
15738
15876
|
setShowPromoDialog(false);
|
|
15739
15877
|
const promoId = config.promo?.discountCode || "default";
|
|
15740
15878
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
@@ -15767,7 +15905,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15767
15905
|
onWidgetLanguage?.(wl);
|
|
15768
15906
|
onTimezone?.(wl.timezone);
|
|
15769
15907
|
}
|
|
15908
|
+
extractGoogleAdsConfig(data);
|
|
15770
15909
|
setEventTypes(data.eventTypes);
|
|
15910
|
+
trackEvent("event_types_loaded", { count: data.eventTypes.length });
|
|
15771
15911
|
if (isSingleEventTypeMode && data.eventTypes.length === 1) {
|
|
15772
15912
|
setSelectedEventType(data.eventTypes[0]);
|
|
15773
15913
|
await loadEventInstances(data.eventTypes[0].id);
|
|
@@ -15803,6 +15943,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15803
15943
|
onWidgetLanguage?.(wl);
|
|
15804
15944
|
onTimezone?.(wl.timezone);
|
|
15805
15945
|
}
|
|
15946
|
+
extractGoogleAdsConfig(data);
|
|
15806
15947
|
setUpcomingEvents(data.upcomingEvents || []);
|
|
15807
15948
|
}
|
|
15808
15949
|
else {
|
|
@@ -15838,6 +15979,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15838
15979
|
onWidgetLanguage?.(wl);
|
|
15839
15980
|
onTimezone?.(wl.timezone);
|
|
15840
15981
|
}
|
|
15982
|
+
extractGoogleAdsConfig(data);
|
|
15841
15983
|
setSpecials(data.specials || []);
|
|
15842
15984
|
}
|
|
15843
15985
|
else {
|
|
@@ -15864,6 +16006,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15864
16006
|
onWidgetLanguage?.(wl);
|
|
15865
16007
|
onTimezone?.(wl.timezone);
|
|
15866
16008
|
}
|
|
16009
|
+
extractGoogleAdsConfig(data);
|
|
15867
16010
|
setEventInstances(data.eventInstances);
|
|
15868
16011
|
if (data.paymentProvider) {
|
|
15869
16012
|
setSystemConfig({
|
|
@@ -15924,6 +16067,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15924
16067
|
onWidgetLanguage?.(wl);
|
|
15925
16068
|
onTimezone?.(wl.timezone);
|
|
15926
16069
|
}
|
|
16070
|
+
extractGoogleAdsConfig(data);
|
|
15927
16071
|
setEventDetails(data.eventDetails);
|
|
15928
16072
|
setSystemConfig({
|
|
15929
16073
|
paymentProvider: data.paymentProvider,
|
|
@@ -15993,6 +16137,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
15993
16137
|
onWidgetLanguage?.(wl);
|
|
15994
16138
|
onTimezone?.(wl.timezone);
|
|
15995
16139
|
}
|
|
16140
|
+
extractGoogleAdsConfig(data);
|
|
15996
16141
|
return data.upsells || [];
|
|
15997
16142
|
}
|
|
15998
16143
|
else {
|
|
@@ -16018,6 +16163,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16018
16163
|
}
|
|
16019
16164
|
// Event type selection handlers
|
|
16020
16165
|
const handleEventTypeSelect = async (eventType) => {
|
|
16166
|
+
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16021
16167
|
setSelectedEventType(eventType);
|
|
16022
16168
|
setCurrentStep("eventInstances");
|
|
16023
16169
|
setShouldRenderInstanceSelection(true);
|
|
@@ -16031,6 +16177,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16031
16177
|
};
|
|
16032
16178
|
// Event instance selection handlers
|
|
16033
16179
|
const handleEventInstanceSelect = async (eventInstance) => {
|
|
16180
|
+
trackEvent("event_instance_selected", { eventInstanceId: eventInstance.id, eventInstanceName: eventInstance.name });
|
|
16034
16181
|
setSelectedEventInstance(eventInstance);
|
|
16035
16182
|
bookingReturnStep.current = "eventInstances";
|
|
16036
16183
|
// Set default participant count for upsell calculations
|
|
@@ -16043,9 +16190,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16043
16190
|
try {
|
|
16044
16191
|
const availableUpsells = await loadUpsells(selectedEventType.id, eventInstance.id, defaultParticipantCount);
|
|
16045
16192
|
if (availableUpsells.length > 0) {
|
|
16046
|
-
|
|
16193
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16047
16194
|
setUpsells(availableUpsells);
|
|
16048
|
-
// Pre-select default-checked upsells
|
|
16049
16195
|
const defaultSelections = availableUpsells
|
|
16050
16196
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16051
16197
|
.map((upsell) => ({
|
|
@@ -16055,7 +16201,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16055
16201
|
setSelectedUpsells(defaultSelections);
|
|
16056
16202
|
setCurrentStep("upsells");
|
|
16057
16203
|
setIsLoadingUpsells(false);
|
|
16058
|
-
return;
|
|
16204
|
+
return;
|
|
16059
16205
|
}
|
|
16060
16206
|
}
|
|
16061
16207
|
catch (err) {
|
|
@@ -16065,7 +16211,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16065
16211
|
setIsLoadingUpsells(false);
|
|
16066
16212
|
}
|
|
16067
16213
|
}
|
|
16068
|
-
|
|
16214
|
+
trackEvent("booking_form_opened", { fromUpsells: false });
|
|
16069
16215
|
setCurrentStep("booking");
|
|
16070
16216
|
setShouldRenderBookingForm(true);
|
|
16071
16217
|
setIsLoadingEventDetails(true);
|
|
@@ -16088,6 +16234,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16088
16234
|
setEventDetails(null);
|
|
16089
16235
|
};
|
|
16090
16236
|
const handleBookingSuccess = (result) => {
|
|
16237
|
+
trackEvent("booking_completed", { paymentIntentId: result.paymentIntent?.id });
|
|
16091
16238
|
setIsSuccess(true);
|
|
16092
16239
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16093
16240
|
setSidebarOpen(false);
|
|
@@ -16103,7 +16250,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16103
16250
|
setSelectedUpsells(selections);
|
|
16104
16251
|
};
|
|
16105
16252
|
const handleUpsellsContinue = async () => {
|
|
16106
|
-
|
|
16253
|
+
trackEvent("booking_form_opened", { fromUpsells: true });
|
|
16107
16254
|
setCurrentStep("booking");
|
|
16108
16255
|
setShouldRenderBookingForm(true);
|
|
16109
16256
|
setIsLoadingEventDetails(true);
|
|
@@ -16123,8 +16270,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16123
16270
|
};
|
|
16124
16271
|
// Voucher purchase handlers
|
|
16125
16272
|
const handleVoucherCardClick = async () => {
|
|
16273
|
+
trackEvent("voucher_card_clicked");
|
|
16126
16274
|
setPreselectedVoucherEventTypeId(null);
|
|
16127
|
-
// Ensure voucher config and event types are loaded before opening the form
|
|
16128
16275
|
if (!voucherConfig || voucherEventTypes.length === 0) {
|
|
16129
16276
|
await loadVoucherConfig();
|
|
16130
16277
|
}
|
|
@@ -16143,6 +16290,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16143
16290
|
setPreselectedVoucherEventTypeId(null);
|
|
16144
16291
|
};
|
|
16145
16292
|
const handleVoucherSuccess = (result) => {
|
|
16293
|
+
trackEvent("voucher_purchased", { voucherType: result.voucherType });
|
|
16146
16294
|
setVoucherPurchaseResult(result);
|
|
16147
16295
|
setIsVoucherFormOpen(false);
|
|
16148
16296
|
setIsSuccess(true);
|
|
@@ -16225,8 +16373,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16225
16373
|
try {
|
|
16226
16374
|
const availableUpsells = await loadUpsells(eventTypeForUpsells.id, eventInstanceId, defaultParticipantCount);
|
|
16227
16375
|
if (availableUpsells.length > 0) {
|
|
16376
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16228
16377
|
setUpsells(availableUpsells);
|
|
16229
|
-
// Pre-select default-checked upsells
|
|
16230
16378
|
const defaultSelections = availableUpsells
|
|
16231
16379
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16232
16380
|
.map((upsell) => ({
|
|
@@ -16236,7 +16384,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16236
16384
|
setSelectedUpsells(defaultSelections);
|
|
16237
16385
|
setCurrentStep("upsells");
|
|
16238
16386
|
setIsLoadingUpsells(false);
|
|
16239
|
-
// Load event details in background for when user continues past upsells
|
|
16240
16387
|
void loadEventDetails(eventInstanceId);
|
|
16241
16388
|
return;
|
|
16242
16389
|
}
|
|
@@ -16406,7 +16553,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16406
16553
|
url.searchParams.delete("mollie_payment_id");
|
|
16407
16554
|
url.searchParams.delete("mollie_status");
|
|
16408
16555
|
window.history.replaceState({}, "", url.toString());
|
|
16409
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16556
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16410
16557
|
}
|
|
16411
16558
|
if (viewMode === "specials" && showingPreview) {
|
|
16412
16559
|
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: () => {
|
|
@@ -16431,7 +16578,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16431
16578
|
setShouldRenderBookingForm(false);
|
|
16432
16579
|
setSelectedUpsells([]);
|
|
16433
16580
|
setUpsells([]);
|
|
16434
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16581
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16435
16582
|
}
|
|
16436
16583
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
16437
16584
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -16454,7 +16601,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16454
16601
|
url.searchParams.delete("mollie_payment_id");
|
|
16455
16602
|
url.searchParams.delete("mollie_status");
|
|
16456
16603
|
window.history.replaceState({}, "", url.toString());
|
|
16457
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16604
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16458
16605
|
}
|
|
16459
16606
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
16460
16607
|
return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -16500,7 +16647,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16500
16647
|
url.searchParams.delete("mollie_payment_id");
|
|
16501
16648
|
url.searchParams.delete("mollie_status");
|
|
16502
16649
|
window.history.replaceState({}, "", url.toString());
|
|
16503
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16650
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16504
16651
|
}
|
|
16505
16652
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
16506
16653
|
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: {
|
|
@@ -16556,7 +16703,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
|
|
|
16556
16703
|
url.searchParams.delete("mollie_payment_id");
|
|
16557
16704
|
url.searchParams.delete("mollie_status");
|
|
16558
16705
|
window.history.replaceState({}, "", url.toString());
|
|
16559
|
-
}, 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: () => {
|
|
16706
|
+
}, 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: () => {
|
|
16560
16707
|
setIsSuccess(false);
|
|
16561
16708
|
setVoucherPurchaseResult(null);
|
|
16562
16709
|
const url = new URL(window.location.href);
|