@bigz-app/booking-widget 1.3.2 → 1.3.4
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 +113 -8
- package/dist/booking-widget.js.map +1 -1
- package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
- package/dist/index.cjs +113 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +113 -8
- 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/package.json +1 -1
package/dist/booking-widget.js
CHANGED
|
@@ -15516,6 +15516,88 @@
|
|
|
15516
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)] })] }))] }) }));
|
|
15517
15517
|
}
|
|
15518
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
|
+
|
|
15519
15601
|
// Main widget component
|
|
15520
15602
|
function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onTimezone, }) {
|
|
15521
15603
|
const t = useTranslations();
|
|
@@ -15708,6 +15790,21 @@
|
|
|
15708
15790
|
setIsLoadingVoucherConfig(false);
|
|
15709
15791
|
}
|
|
15710
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]);
|
|
15711
15808
|
// Fire widget pageview once when Google Ads config is received from API
|
|
15712
15809
|
const pageviewFiredRef = A$2(false);
|
|
15713
15810
|
y$1(() => {
|
|
@@ -15804,6 +15901,7 @@
|
|
|
15804
15901
|
});
|
|
15805
15902
|
const voucherData = await voucherResponse.json();
|
|
15806
15903
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15904
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "stripe_redirect" });
|
|
15807
15905
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15808
15906
|
setIsSuccess(true);
|
|
15809
15907
|
setSuccessPaymentId(null);
|
|
@@ -15813,6 +15911,7 @@
|
|
|
15813
15911
|
catch {
|
|
15814
15912
|
// Fall back to booking success flow if voucher lookup fails.
|
|
15815
15913
|
}
|
|
15914
|
+
trackEvent("booking_completed", { paymentIntentId: stripeReturn.paymentIntent, source: "stripe_redirect" });
|
|
15816
15915
|
setVoucherPurchaseResult(null);
|
|
15817
15916
|
setSuccessPaymentId(stripeReturn.paymentIntent);
|
|
15818
15917
|
setIsSuccess(true);
|
|
@@ -15850,6 +15949,7 @@
|
|
|
15850
15949
|
});
|
|
15851
15950
|
const voucherData = await voucherResponse.json();
|
|
15852
15951
|
if (voucherResponse.ok && voucherData.voucherResult) {
|
|
15952
|
+
trackEvent("voucher_purchased", { voucherType: voucherData.voucherResult.voucherType, source: "mollie_redirect" });
|
|
15853
15953
|
setVoucherPurchaseResult(voucherData.voucherResult);
|
|
15854
15954
|
setIsSuccess(true);
|
|
15855
15955
|
setSuccessPaymentId(null);
|
|
@@ -15888,6 +15988,7 @@
|
|
|
15888
15988
|
window[globalFlagKey] = true;
|
|
15889
15989
|
const timer = setTimeout(() => {
|
|
15890
15990
|
setShowPromoDialog(true);
|
|
15991
|
+
trackEvent("promo_dialog_shown", { discountCode: config.promo?.discountCode });
|
|
15891
15992
|
}, 1000);
|
|
15892
15993
|
return () => clearTimeout(timer);
|
|
15893
15994
|
}, [config.promo?.enabled, config.promo?.discountCode]);
|
|
@@ -15897,6 +15998,7 @@
|
|
|
15897
15998
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
15898
15999
|
};
|
|
15899
16000
|
const handlePromoCtaClick = () => {
|
|
16001
|
+
trackEvent("promo_cta_clicked", { discountCode: config.promo?.discountCode });
|
|
15900
16002
|
setShowPromoDialog(false);
|
|
15901
16003
|
const promoId = config.promo?.discountCode || "default";
|
|
15902
16004
|
localStorage.setItem(`bigz-promo-${promoId}-shown`, "true");
|
|
@@ -15931,6 +16033,7 @@
|
|
|
15931
16033
|
}
|
|
15932
16034
|
extractGoogleAdsConfig(data);
|
|
15933
16035
|
setEventTypes(data.eventTypes);
|
|
16036
|
+
trackEvent("event_types_loaded", { count: data.eventTypes.length });
|
|
15934
16037
|
if (isSingleEventTypeMode && data.eventTypes.length === 1) {
|
|
15935
16038
|
setSelectedEventType(data.eventTypes[0]);
|
|
15936
16039
|
await loadEventInstances(data.eventTypes[0].id);
|
|
@@ -16186,6 +16289,7 @@
|
|
|
16186
16289
|
}
|
|
16187
16290
|
// Event type selection handlers
|
|
16188
16291
|
const handleEventTypeSelect = async (eventType) => {
|
|
16292
|
+
trackEvent("event_type_selected", { eventTypeId: eventType.id, eventTypeName: eventType.name });
|
|
16189
16293
|
setSelectedEventType(eventType);
|
|
16190
16294
|
setCurrentStep("eventInstances");
|
|
16191
16295
|
setShouldRenderInstanceSelection(true);
|
|
@@ -16199,6 +16303,7 @@
|
|
|
16199
16303
|
};
|
|
16200
16304
|
// Event instance selection handlers
|
|
16201
16305
|
const handleEventInstanceSelect = async (eventInstance) => {
|
|
16306
|
+
trackEvent("event_instance_selected", { eventInstanceId: eventInstance.id, eventInstanceName: eventInstance.name });
|
|
16202
16307
|
setSelectedEventInstance(eventInstance);
|
|
16203
16308
|
bookingReturnStep.current = "eventInstances";
|
|
16204
16309
|
// Set default participant count for upsell calculations
|
|
@@ -16211,9 +16316,8 @@
|
|
|
16211
16316
|
try {
|
|
16212
16317
|
const availableUpsells = await loadUpsells(selectedEventType.id, eventInstance.id, defaultParticipantCount);
|
|
16213
16318
|
if (availableUpsells.length > 0) {
|
|
16214
|
-
|
|
16319
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16215
16320
|
setUpsells(availableUpsells);
|
|
16216
|
-
// Pre-select default-checked upsells
|
|
16217
16321
|
const defaultSelections = availableUpsells
|
|
16218
16322
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16219
16323
|
.map((upsell) => ({
|
|
@@ -16223,7 +16327,7 @@
|
|
|
16223
16327
|
setSelectedUpsells(defaultSelections);
|
|
16224
16328
|
setCurrentStep("upsells");
|
|
16225
16329
|
setIsLoadingUpsells(false);
|
|
16226
|
-
return;
|
|
16330
|
+
return;
|
|
16227
16331
|
}
|
|
16228
16332
|
}
|
|
16229
16333
|
catch (err) {
|
|
@@ -16233,7 +16337,7 @@
|
|
|
16233
16337
|
setIsLoadingUpsells(false);
|
|
16234
16338
|
}
|
|
16235
16339
|
}
|
|
16236
|
-
|
|
16340
|
+
trackEvent("booking_form_opened", { fromUpsells: false });
|
|
16237
16341
|
setCurrentStep("booking");
|
|
16238
16342
|
setShouldRenderBookingForm(true);
|
|
16239
16343
|
setIsLoadingEventDetails(true);
|
|
@@ -16256,6 +16360,7 @@
|
|
|
16256
16360
|
setEventDetails(null);
|
|
16257
16361
|
};
|
|
16258
16362
|
const handleBookingSuccess = (result) => {
|
|
16363
|
+
trackEvent("booking_completed", { paymentIntentId: result.paymentIntent?.id });
|
|
16259
16364
|
setIsSuccess(true);
|
|
16260
16365
|
setSuccessPaymentId(result.paymentIntent.id);
|
|
16261
16366
|
setSidebarOpen(false);
|
|
@@ -16271,7 +16376,7 @@
|
|
|
16271
16376
|
setSelectedUpsells(selections);
|
|
16272
16377
|
};
|
|
16273
16378
|
const handleUpsellsContinue = async () => {
|
|
16274
|
-
|
|
16379
|
+
trackEvent("booking_form_opened", { fromUpsells: true });
|
|
16275
16380
|
setCurrentStep("booking");
|
|
16276
16381
|
setShouldRenderBookingForm(true);
|
|
16277
16382
|
setIsLoadingEventDetails(true);
|
|
@@ -16291,8 +16396,8 @@
|
|
|
16291
16396
|
};
|
|
16292
16397
|
// Voucher purchase handlers
|
|
16293
16398
|
const handleVoucherCardClick = async () => {
|
|
16399
|
+
trackEvent("voucher_card_clicked");
|
|
16294
16400
|
setPreselectedVoucherEventTypeId(null);
|
|
16295
|
-
// Ensure voucher config and event types are loaded before opening the form
|
|
16296
16401
|
if (!voucherConfig || voucherEventTypes.length === 0) {
|
|
16297
16402
|
await loadVoucherConfig();
|
|
16298
16403
|
}
|
|
@@ -16311,6 +16416,7 @@
|
|
|
16311
16416
|
setPreselectedVoucherEventTypeId(null);
|
|
16312
16417
|
};
|
|
16313
16418
|
const handleVoucherSuccess = (result) => {
|
|
16419
|
+
trackEvent("voucher_purchased", { voucherType: result.voucherType });
|
|
16314
16420
|
setVoucherPurchaseResult(result);
|
|
16315
16421
|
setIsVoucherFormOpen(false);
|
|
16316
16422
|
setIsSuccess(true);
|
|
@@ -16393,8 +16499,8 @@
|
|
|
16393
16499
|
try {
|
|
16394
16500
|
const availableUpsells = await loadUpsells(eventTypeForUpsells.id, eventInstanceId, defaultParticipantCount);
|
|
16395
16501
|
if (availableUpsells.length > 0) {
|
|
16502
|
+
trackEvent("upsell_step_viewed", { count: availableUpsells.length });
|
|
16396
16503
|
setUpsells(availableUpsells);
|
|
16397
|
-
// Pre-select default-checked upsells
|
|
16398
16504
|
const defaultSelections = availableUpsells
|
|
16399
16505
|
.filter((upsell) => upsell.defaultChecked && upsell.available)
|
|
16400
16506
|
.map((upsell) => ({
|
|
@@ -16404,7 +16510,6 @@
|
|
|
16404
16510
|
setSelectedUpsells(defaultSelections);
|
|
16405
16511
|
setCurrentStep("upsells");
|
|
16406
16512
|
setIsLoadingUpsells(false);
|
|
16407
|
-
// Load event details in background for when user continues past upsells
|
|
16408
16513
|
void loadEventDetails(eventInstanceId);
|
|
16409
16514
|
return;
|
|
16410
16515
|
}
|