@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/booking-widget.js
CHANGED
|
@@ -12134,121 +12134,145 @@
|
|
|
12134
12134
|
}
|
|
12135
12135
|
|
|
12136
12136
|
/**
|
|
12137
|
-
* Google Ads
|
|
12137
|
+
* Google Ads Tracking Utility
|
|
12138
12138
|
*
|
|
12139
|
-
*
|
|
12140
|
-
|
|
12141
|
-
/**
|
|
12142
|
-
* Check if gtag is available in current or parent window
|
|
12139
|
+
* Handles pageview tracking (widget load) and conversion tracking (successful booking).
|
|
12140
|
+
* Supports both direct gtag.js and GTM dataLayer setups.
|
|
12143
12141
|
*/
|
|
12144
12142
|
function isGtagAvailable() {
|
|
12145
|
-
if (typeof window === "undefined")
|
|
12143
|
+
if (typeof window === "undefined")
|
|
12146
12144
|
return false;
|
|
12147
|
-
|
|
12148
|
-
// Check current window
|
|
12149
|
-
if (typeof window.gtag === "function") {
|
|
12145
|
+
if (typeof window.gtag === "function")
|
|
12150
12146
|
return true;
|
|
12151
|
-
}
|
|
12152
|
-
// Check parent window (for iframe/widget scenarios)
|
|
12153
12147
|
if (window !== window.parent) {
|
|
12154
12148
|
try {
|
|
12155
|
-
if (typeof window.parent?.gtag === "function")
|
|
12149
|
+
if (typeof window.parent?.gtag === "function")
|
|
12156
12150
|
return true;
|
|
12157
|
-
}
|
|
12158
12151
|
}
|
|
12159
12152
|
catch {
|
|
12160
|
-
//
|
|
12153
|
+
// Cross-origin
|
|
12161
12154
|
}
|
|
12162
12155
|
}
|
|
12163
12156
|
return false;
|
|
12164
12157
|
}
|
|
12165
|
-
/**
|
|
12166
|
-
* Initialize gtag if not already available
|
|
12167
|
-
*/
|
|
12168
12158
|
function initializeGtag(tagId) {
|
|
12169
|
-
if (typeof window === "undefined")
|
|
12159
|
+
if (typeof window === "undefined" || isGtagAvailable())
|
|
12170
12160
|
return;
|
|
12171
|
-
}
|
|
12172
|
-
// Skip if gtag already exists
|
|
12173
|
-
if (isGtagAvailable()) {
|
|
12174
|
-
return;
|
|
12175
|
-
}
|
|
12176
|
-
// Initialize dataLayer and gtag function
|
|
12177
12161
|
window.dataLayer = window.dataLayer || [];
|
|
12178
12162
|
window.gtag = (...args) => {
|
|
12179
12163
|
window.dataLayer.push(args);
|
|
12180
12164
|
};
|
|
12181
|
-
// Set current timestamp
|
|
12182
12165
|
window.gtag("js", new Date());
|
|
12183
|
-
// Load gtag script
|
|
12184
12166
|
const script = document.createElement("script");
|
|
12185
12167
|
script.async = true;
|
|
12186
12168
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${tagId}`;
|
|
12187
12169
|
document.head.appendChild(script);
|
|
12188
|
-
// Configure the tag
|
|
12189
12170
|
window.gtag("config", tagId, {
|
|
12190
12171
|
anonymize_ip: true,
|
|
12191
12172
|
allow_google_signals: false,
|
|
12192
12173
|
allow_ad_personalization_signals: false,
|
|
12193
12174
|
});
|
|
12194
12175
|
}
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
return;
|
|
12176
|
+
function getGtag() {
|
|
12177
|
+
if (typeof window === "undefined")
|
|
12178
|
+
return null;
|
|
12179
|
+
if (typeof window.gtag === "function") {
|
|
12180
|
+
return window.gtag;
|
|
12201
12181
|
}
|
|
12202
|
-
|
|
12203
|
-
// Try parent window gtag if current window doesn't have it
|
|
12204
|
-
if (typeof gtag !== "function" && window !== window.parent) {
|
|
12182
|
+
if (window !== window.parent) {
|
|
12205
12183
|
try {
|
|
12206
|
-
|
|
12184
|
+
const parentGtag = window.parent?.gtag;
|
|
12185
|
+
if (typeof parentGtag === "function")
|
|
12186
|
+
return parentGtag;
|
|
12207
12187
|
}
|
|
12208
12188
|
catch {
|
|
12209
|
-
//
|
|
12189
|
+
// Cross-origin
|
|
12210
12190
|
}
|
|
12211
12191
|
}
|
|
12212
|
-
|
|
12192
|
+
return null;
|
|
12193
|
+
}
|
|
12194
|
+
/**
|
|
12195
|
+
* Push an event to the dataLayer for GTM visibility,
|
|
12196
|
+
* then also fire via gtag for direct Google Ads tracking.
|
|
12197
|
+
*/
|
|
12198
|
+
function sendEvent(eventName, params) {
|
|
12199
|
+
if (typeof window === "undefined")
|
|
12213
12200
|
return;
|
|
12201
|
+
// GTM dataLayer push (object format — visible in Tag Assistant)
|
|
12202
|
+
window.dataLayer = window.dataLayer || [];
|
|
12203
|
+
window.dataLayer.push({
|
|
12204
|
+
event: eventName,
|
|
12205
|
+
...params,
|
|
12206
|
+
});
|
|
12207
|
+
// gtag call (array format — processed by gtag.js for Google Ads)
|
|
12208
|
+
const gtag = getGtag();
|
|
12209
|
+
if (gtag) {
|
|
12210
|
+
gtag("event", eventName, params);
|
|
12214
12211
|
}
|
|
12215
|
-
|
|
12216
|
-
|
|
12212
|
+
}
|
|
12213
|
+
function sendConversion(config) {
|
|
12214
|
+
const params = {
|
|
12217
12215
|
send_to: `${config.tagId}/${config.conversionId}`,
|
|
12218
12216
|
};
|
|
12219
|
-
// Add optional parameters
|
|
12220
12217
|
if (config.conversionValue !== undefined) {
|
|
12221
|
-
|
|
12218
|
+
params.value = config.conversionValue;
|
|
12222
12219
|
}
|
|
12223
12220
|
if (config.conversionCurrency) {
|
|
12224
|
-
|
|
12221
|
+
params.currency = config.conversionCurrency;
|
|
12225
12222
|
}
|
|
12226
12223
|
if (config.transactionId) {
|
|
12227
|
-
|
|
12224
|
+
params.transaction_id = config.transactionId;
|
|
12228
12225
|
}
|
|
12229
|
-
|
|
12230
|
-
gtag("event", "conversion", conversionData);
|
|
12226
|
+
sendEvent("conversion", params);
|
|
12231
12227
|
}
|
|
12232
12228
|
/**
|
|
12233
|
-
*
|
|
12234
|
-
* Waits 1500ms, checks/initializes gtag, then sends conversion
|
|
12229
|
+
* Track widget pageview (fired once on widget mount).
|
|
12235
12230
|
*/
|
|
12236
|
-
function
|
|
12237
|
-
|
|
12238
|
-
if (!config.tagId || !config.conversionId) {
|
|
12231
|
+
function handleGoogleAdsPageview(tagId, consent) {
|
|
12232
|
+
if (!tagId || false || typeof window === "undefined")
|
|
12239
12233
|
return;
|
|
12234
|
+
if (!isGtagAvailable()) {
|
|
12235
|
+
initializeGtag(tagId);
|
|
12240
12236
|
}
|
|
12241
|
-
|
|
12237
|
+
const fire = () => sendEvent("widget_pageview", {
|
|
12238
|
+
send_to: tagId,
|
|
12239
|
+
page_location: window.location.href,
|
|
12240
|
+
page_title: document.title,
|
|
12241
|
+
});
|
|
12242
|
+
if (isGtagAvailable()) {
|
|
12243
|
+
fire();
|
|
12244
|
+
return;
|
|
12245
|
+
}
|
|
12246
|
+
const script = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${tagId}"]`);
|
|
12247
|
+
if (script) {
|
|
12248
|
+
script.addEventListener("load", fire, { once: true });
|
|
12249
|
+
}
|
|
12250
|
+
}
|
|
12251
|
+
/**
|
|
12252
|
+
* Handle Google Ads conversion tracking.
|
|
12253
|
+
* Waits 1500ms for the success page to settle, then fires.
|
|
12254
|
+
*/
|
|
12255
|
+
function handleGoogleAdsConversion(config) {
|
|
12256
|
+
if (!config.tagId || !config.conversionId)
|
|
12257
|
+
return;
|
|
12242
12258
|
setTimeout(() => {
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12259
|
+
if (isGtagAvailable()) {
|
|
12260
|
+
sendConversion(config);
|
|
12261
|
+
return;
|
|
12262
|
+
}
|
|
12263
|
+
initializeGtag(config.tagId);
|
|
12264
|
+
const script = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${config.tagId}"]`);
|
|
12265
|
+
if (script) {
|
|
12266
|
+
script.addEventListener("load", () => sendConversion(config));
|
|
12267
|
+
script.addEventListener("error", () => sendConversion(config));
|
|
12268
|
+
}
|
|
12269
|
+
else {
|
|
12270
|
+
sendConversion(config);
|
|
12246
12271
|
}
|
|
12247
|
-
sendConversion(config);
|
|
12248
12272
|
}, 1500);
|
|
12249
12273
|
}
|
|
12250
12274
|
|
|
12251
|
-
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, }) => {
|
|
12275
|
+
const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId, googleAdsConfig: googleAdsConfigProp, }) => {
|
|
12252
12276
|
const t = useTranslations();
|
|
12253
12277
|
const { locale } = useLocale();
|
|
12254
12278
|
const timezone = useTimezone();
|
|
@@ -12297,20 +12321,16 @@
|
|
|
12297
12321
|
});
|
|
12298
12322
|
setPaymentStatus(data.stripePaymentIntent?.status || data.order.status);
|
|
12299
12323
|
const finalPaymentStatus = data.stripePaymentIntent?.status || data.order.status;
|
|
12324
|
+
const adsConfig = googleAdsConfigProp ?? data.googleAdsConfig;
|
|
12300
12325
|
if (finalPaymentStatus === "succeeded" &&
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
config.googleAds?.consent !== false) {
|
|
12304
|
-
// Prepare conversion tracking data
|
|
12305
|
-
const conversionValue = data.order.total / 100;
|
|
12306
|
-
const transactionId = data.order.id;
|
|
12307
|
-
// Track the conversion
|
|
12326
|
+
adsConfig?.tagId &&
|
|
12327
|
+
adsConfig?.conversionId) {
|
|
12308
12328
|
handleGoogleAdsConversion({
|
|
12309
|
-
tagId:
|
|
12310
|
-
conversionId:
|
|
12311
|
-
conversionValue,
|
|
12312
|
-
conversionCurrency:
|
|
12313
|
-
transactionId,
|
|
12329
|
+
tagId: adsConfig.tagId,
|
|
12330
|
+
conversionId: adsConfig.conversionId,
|
|
12331
|
+
conversionValue: data.order.total / 100,
|
|
12332
|
+
conversionCurrency: adsConfig.conversionCurrency || "EUR",
|
|
12333
|
+
transactionId: data.order.id,
|
|
12314
12334
|
});
|
|
12315
12335
|
}
|
|
12316
12336
|
}
|
|
@@ -15496,6 +15516,88 @@
|
|
|
15496
15516
|
return (u$2(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: u$2("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (u$2("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), u$2("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (u$2("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: u$2("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (u$2("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (u$2(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (u$2("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: [u$2("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), u$2("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
|
|
15497
15517
|
}
|
|
15498
15518
|
|
|
15519
|
+
/**
|
|
15520
|
+
* Widget analytics — server-side PostHog tracking via batched sendBeacon.
|
|
15521
|
+
*
|
|
15522
|
+
* Events are buffered locally and flushed every few seconds (or on page
|
|
15523
|
+
* unload) as a single POST to `/api/booking/track`. This avoids extra
|
|
15524
|
+
* network requests on every click while still capturing a complete funnel.
|
|
15525
|
+
*/
|
|
15526
|
+
let buffer = [];
|
|
15527
|
+
let flushTimer = null;
|
|
15528
|
+
let currentApiBaseUrl = "";
|
|
15529
|
+
let currentOrganizationId = "";
|
|
15530
|
+
const FLUSH_INTERVAL_MS = 3000;
|
|
15531
|
+
function flush() {
|
|
15532
|
+
if (buffer.length === 0)
|
|
15533
|
+
return;
|
|
15534
|
+
const payload = JSON.stringify({
|
|
15535
|
+
organizationId: currentOrganizationId,
|
|
15536
|
+
events: buffer,
|
|
15537
|
+
});
|
|
15538
|
+
buffer = [];
|
|
15539
|
+
const url = getApiUrl(currentApiBaseUrl, "/booking/track");
|
|
15540
|
+
// Use fetch with keepalive as primary — works cross-origin with CORS.
|
|
15541
|
+
// sendBeacon silently fails cross-origin with application/json because
|
|
15542
|
+
// it can't do CORS preflight, so we only use it as a text/plain fallback
|
|
15543
|
+
// on page unload when fetch may be aborted.
|
|
15544
|
+
try {
|
|
15545
|
+
void fetch(url, {
|
|
15546
|
+
method: "POST",
|
|
15547
|
+
headers: { "Content-Type": "application/json" },
|
|
15548
|
+
body: payload,
|
|
15549
|
+
keepalive: true,
|
|
15550
|
+
});
|
|
15551
|
+
}
|
|
15552
|
+
catch {
|
|
15553
|
+
// fetch failed (e.g. during page unload) — try sendBeacon with text/plain
|
|
15554
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
15555
|
+
const blob = new Blob([payload], { type: "text/plain" });
|
|
15556
|
+
navigator.sendBeacon(url, blob);
|
|
15557
|
+
}
|
|
15558
|
+
}
|
|
15559
|
+
}
|
|
15560
|
+
function scheduleFlush() {
|
|
15561
|
+
if (flushTimer)
|
|
15562
|
+
return;
|
|
15563
|
+
flushTimer = setTimeout(() => {
|
|
15564
|
+
flushTimer = null;
|
|
15565
|
+
flush();
|
|
15566
|
+
}, FLUSH_INTERVAL_MS);
|
|
15567
|
+
}
|
|
15568
|
+
/**
|
|
15569
|
+
* Initialise the analytics module. Must be called once before `trackEvent`.
|
|
15570
|
+
*/
|
|
15571
|
+
function initAnalytics(apiBaseUrl, organizationId) {
|
|
15572
|
+
currentApiBaseUrl = apiBaseUrl;
|
|
15573
|
+
currentOrganizationId = organizationId;
|
|
15574
|
+
if (typeof window !== "undefined") {
|
|
15575
|
+
window.addEventListener("pagehide", flush);
|
|
15576
|
+
window.addEventListener("visibilitychange", () => {
|
|
15577
|
+
if (document.visibilityState === "hidden")
|
|
15578
|
+
flush();
|
|
15579
|
+
});
|
|
15580
|
+
}
|
|
15581
|
+
}
|
|
15582
|
+
/**
|
|
15583
|
+
* Queue a widget event. Non-blocking, fire-and-forget.
|
|
15584
|
+
*/
|
|
15585
|
+
function trackEvent(event, properties = {}) {
|
|
15586
|
+
if (typeof window === "undefined")
|
|
15587
|
+
return;
|
|
15588
|
+
if (!currentOrganizationId)
|
|
15589
|
+
return;
|
|
15590
|
+
buffer.push({
|
|
15591
|
+
event,
|
|
15592
|
+
properties,
|
|
15593
|
+
domain: window.location.hostname,
|
|
15594
|
+
url: window.location.href,
|
|
15595
|
+
referrer: document.referrer,
|
|
15596
|
+
timestamp: new Date().toISOString(),
|
|
15597
|
+
});
|
|
15598
|
+
scheduleFlush();
|
|
15599
|
+
}
|
|
15600
|
+
|
|
15499
15601
|
// Main widget component
|
|
15500
15602
|
function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onTimezone, }) {
|
|
15501
15603
|
const t = useTranslations();
|
|
@@ -15575,6 +15677,13 @@
|
|
|
15575
15677
|
const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = d$1(false);
|
|
15576
15678
|
const [shouldRenderUpsells, setShouldRenderUpsells] = d$1(false);
|
|
15577
15679
|
const [shouldRenderBookingForm, setShouldRenderBookingForm] = d$1(false);
|
|
15680
|
+
// Google Ads config (received from API, set once from the first API response)
|
|
15681
|
+
const [googleAdsConfig, setGoogleAdsConfig] = d$1(null);
|
|
15682
|
+
const extractGoogleAdsConfig = (data) => {
|
|
15683
|
+
if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
|
|
15684
|
+
setGoogleAdsConfig(data.googleAdsConfig);
|
|
15685
|
+
}
|
|
15686
|
+
};
|
|
15578
15687
|
// Promo dialog state
|
|
15579
15688
|
const [showPromoDialog, setShowPromoDialog] = d$1(false);
|
|
15580
15689
|
const [widgetContainerRef, setWidgetContainerRef] = d$1(null);
|
|
@@ -15658,6 +15767,7 @@
|
|
|
15658
15767
|
image: resolvedImage,
|
|
15659
15768
|
};
|
|
15660
15769
|
setVoucherConfig(mergedConfig);
|
|
15770
|
+
extractGoogleAdsConfig(data);
|
|
15661
15771
|
setVoucherEventTypes(data.eventTypes || []);
|
|
15662
15772
|
// Set system config for payment processing
|
|
15663
15773
|
if (data.paymentProvider) {
|
|
@@ -15680,6 +15790,29 @@
|
|
|
15680
15790
|
setIsLoadingVoucherConfig(false);
|
|
15681
15791
|
}
|
|
15682
15792
|
};
|
|
15793
|
+
// Initialise analytics once on mount
|
|
15794
|
+
const analyticsInitRef = A$2(false);
|
|
15795
|
+
y$1(() => {
|
|
15796
|
+
if (!analyticsInitRef.current && config.organizationId) {
|
|
15797
|
+
analyticsInitRef.current = true;
|
|
15798
|
+
initAnalytics(config.apiBaseUrl, config.organizationId);
|
|
15799
|
+
trackEvent("widget_loaded", {
|
|
15800
|
+
viewMode,
|
|
15801
|
+
eventTypeId: config.eventTypeId,
|
|
15802
|
+
categoryId: config.categoryId,
|
|
15803
|
+
eventInstanceId: config.eventInstanceId,
|
|
15804
|
+
isStandaloneVoucherMode,
|
|
15805
|
+
});
|
|
15806
|
+
}
|
|
15807
|
+
}, [config.organizationId, config.apiBaseUrl]);
|
|
15808
|
+
// Fire widget pageview once when Google Ads config is received from API
|
|
15809
|
+
const pageviewFiredRef = A$2(false);
|
|
15810
|
+
y$1(() => {
|
|
15811
|
+
if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
|
|
15812
|
+
pageviewFiredRef.current = true;
|
|
15813
|
+
handleGoogleAdsPageview(googleAdsConfig.tagId);
|
|
15814
|
+
}
|
|
15815
|
+
}, [googleAdsConfig]);
|
|
15683
15816
|
// Determine initial step and load data
|
|
15684
15817
|
y$1(() => {
|
|
15685
15818
|
const initializeWidget = async () => {
|
|
@@ -15768,6 +15901,7 @@
|
|
|
15768
15901
|
});
|
|
15769
15902
|
const voucherData = await voucherResponse.json();
|
|
15770
15903
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15904
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "stripe_redirect" });
|
|
15771
15905
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15772
15906
|
setIsSuccess(true);
|
|
15773
15907
|
setSuccessPaymentId(null);
|
|
@@ -15777,6 +15911,7 @@
|
|
|
15777
15911
|
catch {
|
|
15778
15912
|
// Fall back to booking success flow if voucher lookup fails.
|
|
15779
15913
|
}
|
|
15914
|
+
trackEvent("booking_completed", { paymentIntentId: stripeReturn.paymentIntent, source: "stripe_redirect" });
|
|
15780
15915
|
setVoucherPurchaseResult(null);
|
|
15781
15916
|
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
15782
15917
|
setIsSuccess(true);
|
|
@@ -15814,6 +15949,7 @@
|
|
|
15814
15949
|
});
|
|
15815
15950
|
const voucherData = await voucherResponse.json();
|
|
15816
15951
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15952
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "mollie_redirect" });
|
|
15817
15953
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15818
15954
|
setIsSuccess(true);
|
|
15819
15955
|
setSuccessPaymentId(null);
|
|
@@ -15852,6 +15988,7 @@
|
|
|
15852
15988
|
window[globalFlagKey] = true;
|
|
15853
15989
|
const timer = setTimeout(() => {
|
|
15854
15990
|
setShowPromoDialog(true);
|
|
15991
|
+
trackEvent("promo_dialog_shown", { discountCode: config.promo?.discountCode });
|
|
15855
15992
|
}, 1000);
|
|
15856
15993
|
return () => clearTimeout(timer);
|
|
15857
15994
|
}, [config.promo?.enabled, config.promo?.discountCode]);
|
|
@@ -15861,6 +15998,7 @@
|
|
|
15861
15998
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
15862
15999
|
};
|
|
15863
16000
|
const handlePromoCtaClick = () => {
|
|
16001
|
+
trackEvent("promo_cta_clicked", { discountCode: config.promo?.discountCode });
|
|
15864
16002
|
setShowPromoDialog(false);
|
|
15865
16003
|
const promoId = config.promo?.discountCode || "default";
|
|
15866
16004
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
@@ -15893,7 +16031,9 @@
|
|
|
15893
16031
|
onWidgetLanguage?.(wl);
|
|
15894
16032
|
onTimezone?.(wl.timezone);
|
|
15895
16033
|
}
|
|
16034
|
+
extractGoogleAdsConfig(data);
|
|
15896
16035
|
setEventTypes(data.eventTypes);
|
|
16036
|
+
trackEvent("event_types_loaded", { count: data.eventTypes.length });
|
|
15897
16037
|
if (isSingleEventTypeMode && data.eventTypes.length === 1) {
|
|
15898
16038
|
setSelectedEventType(data.eventTypes[0]);
|
|
15899
16039
|
await loadEventInstances(data.eventTypes[0].id);
|
|
@@ -15929,6 +16069,7 @@
|
|
|
15929
16069
|
onWidgetLanguage?.(wl);
|
|
15930
16070
|
onTimezone?.(wl.timezone);
|
|
15931
16071
|
}
|
|
16072
|
+
extractGoogleAdsConfig(data);
|
|
15932
16073
|
setUpcomingEvents(data.upcomingEvents || []);
|
|
15933
16074
|
}
|
|
15934
16075
|
else {
|
|
@@ -15964,6 +16105,7 @@
|
|
|
15964
16105
|
onWidgetLanguage?.(wl);
|
|
15965
16106
|
onTimezone?.(wl.timezone);
|
|
15966
16107
|
}
|
|
16108
|
+
extractGoogleAdsConfig(data);
|
|
15967
16109
|
setSpecials(data.specials || []);
|
|
15968
16110
|
}
|
|
15969
16111
|
else {
|
|
@@ -15990,6 +16132,7 @@
|
|
|
15990
16132
|
onWidgetLanguage?.(wl);
|
|
15991
16133
|
onTimezone?.(wl.timezone);
|
|
15992
16134
|
}
|
|
16135
|
+
extractGoogleAdsConfig(data);
|
|
15993
16136
|
setEventInstances(data.eventInstances);
|
|
15994
16137
|
if (data.paymentProvider) {
|
|
15995
16138
|
setSystemConfig({
|
|
@@ -16050,6 +16193,7 @@
|
|
|
16050
16193
|
onWidgetLanguage?.(wl);
|
|
16051
16194
|
onTimezone?.(wl.timezone);
|
|
16052
16195
|
}
|
|
16196
|
+
extractGoogleAdsConfig(data);
|
|
16053
16197
|
setEventDetails(data.eventDetails);
|
|
16054
16198
|
setSystemConfig({
|
|
16055
16199
|
paymentProvider: data.paymentProvider,
|
|
@@ -16119,6 +16263,7 @@
|
|
|
16119
16263
|
onWidgetLanguage?.(wl);
|
|
16120
16264
|
onTimezone?.(wl.timezone);
|
|
16121
16265
|
}
|
|
16266
|
+
extractGoogleAdsConfig(data);
|
|
16122
16267
|
return data.upsells || [];
|
|
16123
16268
|
}
|
|
16124
16269
|
else {
|
|
@@ -16144,6 +16289,7 @@
|
|
|
16144
16289
|
}
|
|
16145
16290
|
// Event type selection handlers
|
|
16146
16291
|
const handleEventTypeSelect = async (eventType) => {
|
|
16292
|
+
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16147
16293
|
setSelectedEventType(eventType);
|
|
16148
16294
|
setCurrentStep("eventInstances");
|
|
16149
16295
|
setShouldRenderInstanceSelection(true);
|
|
@@ -16157,6 +16303,7 @@
|
|
|
16157
16303
|
};
|
|
16158
16304
|
// Event instance selection handlers
|
|
16159
16305
|
const handleEventInstanceSelect = async (eventInstance) => {
|
|
16306
|
+
trackEvent("event_instance_selected", { eventInstanceId: eventInstance.id, eventInstanceName: eventInstance.name });
|
|
16160
16307
|
setSelectedEventInstance(eventInstance);
|
|
16161
16308
|
bookingReturnStep.current = "eventInstances";
|
|
16162
16309
|
// Set default participant count for upsell calculations
|
|
@@ -16169,9 +16316,8 @@
|
|
|
16169
16316
|
try {
|
|
16170
16317
|
const availableUpsells = await loadUpsells(selectedEventType.id, eventInstance.id, defaultParticipantCount);
|
|
16171
16318
|
if (availableUpsells.length > 0) {
|
|
16172
|
-
|
|
16319
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16173
16320
|
setUpsells(availableUpsells);
|
|
16174
|
-
// Pre-select default-checked upsells
|
|
16175
16321
|
const defaultSelections = availableUpsells
|
|
16176
16322
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16177
16323
|
.map((upsell) => ({
|
|
@@ -16181,7 +16327,7 @@
|
|
|
16181
16327
|
setSelectedUpsells(defaultSelections);
|
|
16182
16328
|
setCurrentStep("upsells");
|
|
16183
16329
|
setIsLoadingUpsells(false);
|
|
16184
|
-
return;
|
|
16330
|
+
return;
|
|
16185
16331
|
}
|
|
16186
16332
|
}
|
|
16187
16333
|
catch (err) {
|
|
@@ -16191,7 +16337,7 @@
|
|
|
16191
16337
|
setIsLoadingUpsells(false);
|
|
16192
16338
|
}
|
|
16193
16339
|
}
|
|
16194
|
-
|
|
16340
|
+
trackEvent("booking_form_opened", { fromUpsells: false });
|
|
16195
16341
|
setCurrentStep("booking");
|
|
16196
16342
|
setShouldRenderBookingForm(true);
|
|
16197
16343
|
setIsLoadingEventDetails(true);
|
|
@@ -16214,6 +16360,7 @@
|
|
|
16214
16360
|
setEventDetails(null);
|
|
16215
16361
|
};
|
|
16216
16362
|
const handleBookingSuccess = (result) => {
|
|
16363
|
+
trackEvent("booking_completed", { paymentIntentId: result.paymentIntent?.id });
|
|
16217
16364
|
setIsSuccess(true);
|
|
16218
16365
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16219
16366
|
setSidebarOpen(false);
|
|
@@ -16229,7 +16376,7 @@
|
|
|
16229
16376
|
setSelectedUpsells(selections);
|
|
16230
16377
|
};
|
|
16231
16378
|
const handleUpsellsContinue = async () => {
|
|
16232
|
-
|
|
16379
|
+
trackEvent("booking_form_opened", { fromUpsells: true });
|
|
16233
16380
|
setCurrentStep("booking");
|
|
16234
16381
|
setShouldRenderBookingForm(true);
|
|
16235
16382
|
setIsLoadingEventDetails(true);
|
|
@@ -16249,8 +16396,8 @@
|
|
|
16249
16396
|
};
|
|
16250
16397
|
// Voucher purchase handlers
|
|
16251
16398
|
const handleVoucherCardClick = async () => {
|
|
16399
|
+
trackEvent("voucher_card_clicked");
|
|
16252
16400
|
setPreselectedVoucherEventTypeId(null);
|
|
16253
|
-
// Ensure voucher config and event types are loaded before opening the form
|
|
16254
16401
|
if (!voucherConfig || voucherEventTypes.length === 0) {
|
|
16255
16402
|
await loadVoucherConfig();
|
|
16256
16403
|
}
|
|
@@ -16269,6 +16416,7 @@
|
|
|
16269
16416
|
setPreselectedVoucherEventTypeId(null);
|
|
16270
16417
|
};
|
|
16271
16418
|
const handleVoucherSuccess = (result) => {
|
|
16419
|
+
trackEvent("voucher_purchased", { voucherType: result.voucherType });
|
|
16272
16420
|
setVoucherPurchaseResult(result);
|
|
16273
16421
|
setIsVoucherFormOpen(false);
|
|
16274
16422
|
setIsSuccess(true);
|
|
@@ -16351,8 +16499,8 @@
|
|
|
16351
16499
|
try {
|
|
16352
16500
|
const availableUpsells = await loadUpsells(eventTypeForUpsells.id, eventInstanceId, defaultParticipantCount);
|
|
16353
16501
|
if (availableUpsells.length > 0) {
|
|
16502
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16354
16503
|
setUpsells(availableUpsells);
|
|
16355
|
-
// Pre-select default-checked upsells
|
|
16356
16504
|
const defaultSelections = availableUpsells
|
|
16357
16505
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16358
16506
|
.map((upsell) => ({
|
|
@@ -16362,7 +16510,6 @@
|
|
|
16362
16510
|
setSelectedUpsells(defaultSelections);
|
|
16363
16511
|
setCurrentStep("upsells");
|
|
16364
16512
|
setIsLoadingUpsells(false);
|
|
16365
|
-
// Load event details in background for when user continues past upsells
|
|
16366
16513
|
void loadEventDetails(eventInstanceId);
|
|
16367
16514
|
return;
|
|
16368
16515
|
}
|
|
@@ -16532,7 +16679,7 @@
|
|
|
16532
16679
|
url.searchParams.delete("mollie_payment_id");
|
|
16533
16680
|
url.searchParams.delete("mollie_status");
|
|
16534
16681
|
window.history.replaceState({}, "", url.toString());
|
|
16535
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16682
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16536
16683
|
}
|
|
16537
16684
|
if (viewMode === "specials" && showingPreview) {
|
|
16538
16685
|
return (u$2(StyleProvider, { config: config, children: [u$2("div", { ref: setWidgetContainerRef, children: [u$2(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (u$2(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => {
|
|
@@ -16557,7 +16704,7 @@
|
|
|
16557
16704
|
setShouldRenderBookingForm(false);
|
|
16558
16705
|
setSelectedUpsells([]);
|
|
16559
16706
|
setUpsells([]);
|
|
16560
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16707
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16561
16708
|
}
|
|
16562
16709
|
if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
|
|
16563
16710
|
return (u$2(StyleProvider, { config: config, children: [u$2("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (u$2(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
|
|
@@ -16580,7 +16727,7 @@
|
|
|
16580
16727
|
url.searchParams.delete("mollie_payment_id");
|
|
16581
16728
|
url.searchParams.delete("mollie_status");
|
|
16582
16729
|
window.history.replaceState({}, "", url.toString());
|
|
16583
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16730
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16584
16731
|
}
|
|
16585
16732
|
if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
|
|
16586
16733
|
return (u$2(StyleProvider, { config: config, children: [u$2("div", { ref: setWidgetContainerRef, style: {
|
|
@@ -16626,7 +16773,7 @@
|
|
|
16626
16773
|
url.searchParams.delete("mollie_payment_id");
|
|
16627
16774
|
url.searchParams.delete("mollie_status");
|
|
16628
16775
|
window.history.replaceState({}, "", url.toString());
|
|
16629
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16776
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
|
|
16630
16777
|
}
|
|
16631
16778
|
// Cards mode (default) - show event type selection with optional voucher card
|
|
16632
16779
|
const cardsView = (u$2(k$3, { children: [hasEventSelection && (u$2(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 && (u$2(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 && (u$2("div", { style: { padding: "24px", textAlign: "center" }, children: u$2("div", { style: {
|
|
@@ -16682,7 +16829,7 @@
|
|
|
16682
16829
|
url.searchParams.delete("mollie_payment_id");
|
|
16683
16830
|
url.searchParams.delete("mollie_status");
|
|
16684
16831
|
window.history.replaceState({}, "", url.toString());
|
|
16685
|
-
}, config: config, onError: setError, paymentIntentId: successPaymentId }), u$2(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: () => {
|
|
16832
|
+
}, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId }), u$2(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: () => {
|
|
16686
16833
|
setIsSuccess(false);
|
|
16687
16834
|
setVoucherPurchaseResult(null);
|
|
16688
16835
|
const url = new URL(window.location.href);
|