@bigz-app/booking-widget 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12134,121 +12134,145 @@
12134
12134
  }
12135
12135
 
12136
12136
  /**
12137
- * Google Ads Conversion Tracking Utility
12137
+ * Google Ads Tracking Utility
12138
12138
  *
12139
- * Simplified utility that waits 1500ms, checks/initializes gtag, and sends conversion.
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
- // Cannot access parent window (cross-origin)
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") {
12170
- return;
12171
- }
12172
- // Skip if gtag already exists
12173
- if (isGtagAvailable()) {
12159
+ if (typeof window === "undefined" || isGtagAvailable())
12174
12160
  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
- * Send conversion event using available gtag
12197
- */
12198
- function sendConversion(config) {
12199
- if (typeof window === "undefined") {
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
- let gtag = window.gtag;
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
- gtag = window.parent?.gtag;
12184
+ const parentGtag = window.parent?.gtag;
12185
+ if (typeof parentGtag === "function")
12186
+ return parentGtag;
12207
12187
  }
12208
12188
  catch {
12209
- // Cannot access parent window (cross-origin)
12189
+ // Cross-origin
12210
12190
  }
12211
12191
  }
12212
- if (typeof gtag !== "function") {
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
- // Build conversion data
12216
- const conversionData = {
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
- conversionData.value = config.conversionValue;
12218
+ params.value = config.conversionValue;
12222
12219
  }
12223
12220
  if (config.conversionCurrency) {
12224
- conversionData.currency = config.conversionCurrency;
12221
+ params.currency = config.conversionCurrency;
12225
12222
  }
12226
12223
  if (config.transactionId) {
12227
- conversionData.transaction_id = config.transactionId;
12224
+ params.transaction_id = config.transactionId;
12228
12225
  }
12229
- // Send conversion event
12230
- gtag("event", "conversion", conversionData);
12226
+ sendEvent("conversion", params);
12231
12227
  }
12232
12228
  /**
12233
- * Main function to handle Google Ads conversion tracking
12234
- * Waits 1500ms, checks/initializes gtag, then sends conversion
12229
+ * Track widget pageview (fired once on widget mount).
12235
12230
  */
12236
- function handleGoogleAdsConversion(config) {
12237
- // Validate required config
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
- // Wait 1500ms before proceeding
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
- // Check if gtag is available, initialize if not
12244
- if (!isGtagAvailable()) {
12245
- initializeGtag(config.tagId);
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
- config.googleAds?.tagId &&
12302
- config.googleAds?.conversionId &&
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: config.googleAds.tagId,
12310
- conversionId: config.googleAds.conversionId,
12311
- conversionValue,
12312
- conversionCurrency: config.googleAds.conversionCurrency || "EUR",
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
  }
@@ -15575,6 +15595,13 @@
15575
15595
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = d$1(false);
15576
15596
  const [shouldRenderUpsells, setShouldRenderUpsells] = d$1(false);
15577
15597
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = d$1(false);
15598
+ // Google Ads config (received from API, set once from the first API response)
15599
+ const [googleAdsConfig, setGoogleAdsConfig] = d$1(null);
15600
+ const extractGoogleAdsConfig = (data) => {
15601
+ if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
15602
+ setGoogleAdsConfig(data.googleAdsConfig);
15603
+ }
15604
+ };
15578
15605
  // Promo dialog state
15579
15606
  const [showPromoDialog, setShowPromoDialog] = d$1(false);
15580
15607
  const [widgetContainerRef, setWidgetContainerRef] = d$1(null);
@@ -15658,6 +15685,7 @@
15658
15685
  image: resolvedImage,
15659
15686
  };
15660
15687
  setVoucherConfig(mergedConfig);
15688
+ extractGoogleAdsConfig(data);
15661
15689
  setVoucherEventTypes(data.eventTypes || []);
15662
15690
  // Set system config for payment processing
15663
15691
  if (data.paymentProvider) {
@@ -15680,6 +15708,14 @@
15680
15708
  setIsLoadingVoucherConfig(false);
15681
15709
  }
15682
15710
  };
15711
+ // Fire widget pageview once when Google Ads config is received from API
15712
+ const pageviewFiredRef = A$2(false);
15713
+ y$1(() => {
15714
+ if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
15715
+ pageviewFiredRef.current = true;
15716
+ handleGoogleAdsPageview(googleAdsConfig.tagId);
15717
+ }
15718
+ }, [googleAdsConfig]);
15683
15719
  // Determine initial step and load data
15684
15720
  y$1(() => {
15685
15721
  const initializeWidget = async () => {
@@ -15893,6 +15929,7 @@
15893
15929
  onWidgetLanguage?.(wl);
15894
15930
  onTimezone?.(wl.timezone);
15895
15931
  }
15932
+ extractGoogleAdsConfig(data);
15896
15933
  setEventTypes(data.eventTypes);
15897
15934
  if (isSingleEventTypeMode && data.eventTypes.length === 1) {
15898
15935
  setSelectedEventType(data.eventTypes[0]);
@@ -15929,6 +15966,7 @@
15929
15966
  onWidgetLanguage?.(wl);
15930
15967
  onTimezone?.(wl.timezone);
15931
15968
  }
15969
+ extractGoogleAdsConfig(data);
15932
15970
  setUpcomingEvents(data.upcomingEvents || []);
15933
15971
  }
15934
15972
  else {
@@ -15964,6 +16002,7 @@
15964
16002
  onWidgetLanguage?.(wl);
15965
16003
  onTimezone?.(wl.timezone);
15966
16004
  }
16005
+ extractGoogleAdsConfig(data);
15967
16006
  setSpecials(data.specials || []);
15968
16007
  }
15969
16008
  else {
@@ -15990,6 +16029,7 @@
15990
16029
  onWidgetLanguage?.(wl);
15991
16030
  onTimezone?.(wl.timezone);
15992
16031
  }
16032
+ extractGoogleAdsConfig(data);
15993
16033
  setEventInstances(data.eventInstances);
15994
16034
  if (data.paymentProvider) {
15995
16035
  setSystemConfig({
@@ -16050,6 +16090,7 @@
16050
16090
  onWidgetLanguage?.(wl);
16051
16091
  onTimezone?.(wl.timezone);
16052
16092
  }
16093
+ extractGoogleAdsConfig(data);
16053
16094
  setEventDetails(data.eventDetails);
16054
16095
  setSystemConfig({
16055
16096
  paymentProvider: data.paymentProvider,
@@ -16119,6 +16160,7 @@
16119
16160
  onWidgetLanguage?.(wl);
16120
16161
  onTimezone?.(wl.timezone);
16121
16162
  }
16163
+ extractGoogleAdsConfig(data);
16122
16164
  return data.upsells || [];
16123
16165
  }
16124
16166
  else {
@@ -16532,7 +16574,7 @@
16532
16574
  url.searchParams.delete("mollie_payment_id");
16533
16575
  url.searchParams.delete("mollie_status");
16534
16576
  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 }))] }));
16577
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16536
16578
  }
16537
16579
  if (viewMode === "specials" && showingPreview) {
16538
16580
  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 +16599,7 @@
16557
16599
  setShouldRenderBookingForm(false);
16558
16600
  setSelectedUpsells([]);
16559
16601
  setUpsells([]);
16560
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16602
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16561
16603
  }
16562
16604
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
16563
16605
  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 +16622,7 @@
16580
16622
  url.searchParams.delete("mollie_payment_id");
16581
16623
  url.searchParams.delete("mollie_status");
16582
16624
  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 }))] }));
16625
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16584
16626
  }
16585
16627
  if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
16586
16628
  return (u$2(StyleProvider, { config: config, children: [u$2("div", { ref: setWidgetContainerRef, style: {
@@ -16626,7 +16668,7 @@
16626
16668
  url.searchParams.delete("mollie_payment_id");
16627
16669
  url.searchParams.delete("mollie_status");
16628
16670
  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 }))] }));
16671
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (u$2(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16630
16672
  }
16631
16673
  // Cards mode (default) - show event type selection with optional voucher card
16632
16674
  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 +16724,7 @@
16682
16724
  url.searchParams.delete("mollie_payment_id");
16683
16725
  url.searchParams.delete("mollie_status");
16684
16726
  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: () => {
16727
+ }, 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
16728
  setIsSuccess(false);
16687
16729
  setVoucherPurchaseResult(null);
16688
16730
  const url = new URL(window.location.href);