@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.
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 Conversion Tracking Utility
12011
+ * Google Ads Tracking Utility
12012
12012
  *
12013
- * Simplified utility that waits 1500ms, checks/initializes gtag, and sends conversion.
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
- // Cannot access parent window (cross-origin)
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") {
12044
- return;
12045
- }
12046
- // Skip if gtag already exists
12047
- if (isGtagAvailable()) {
12033
+ if (typeof window === "undefined" || isGtagAvailable())
12048
12034
  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
- * Send conversion event using available gtag
12071
- */
12072
- function sendConversion(config) {
12073
- if (typeof window === "undefined") {
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
- let gtag = window.gtag;
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
- gtag = window.parent?.gtag;
12058
+ const parentGtag = window.parent?.gtag;
12059
+ if (typeof parentGtag === "function")
12060
+ return parentGtag;
12081
12061
  }
12082
12062
  catch {
12083
- // Cannot access parent window (cross-origin)
12063
+ // Cross-origin
12084
12064
  }
12085
12065
  }
12086
- if (typeof gtag !== "function") {
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
- // Build conversion data
12090
- const conversionData = {
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
- conversionData.value = config.conversionValue;
12092
+ params.value = config.conversionValue;
12096
12093
  }
12097
12094
  if (config.conversionCurrency) {
12098
- conversionData.currency = config.conversionCurrency;
12095
+ params.currency = config.conversionCurrency;
12099
12096
  }
12100
12097
  if (config.transactionId) {
12101
- conversionData.transaction_id = config.transactionId;
12098
+ params.transaction_id = config.transactionId;
12102
12099
  }
12103
- // Send conversion event
12104
- gtag("event", "conversion", conversionData);
12100
+ sendEvent("conversion", params);
12105
12101
  }
12106
12102
  /**
12107
- * Main function to handle Google Ads conversion tracking
12108
- * Waits 1500ms, checks/initializes gtag, then sends conversion
12103
+ * Track widget pageview (fired once on widget mount).
12109
12104
  */
12110
- function handleGoogleAdsConversion(config) {
12111
- // Validate required config
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
- // Wait 1500ms before proceeding
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
- // Check if gtag is available, initialize if not
12118
- if (!isGtagAvailable()) {
12119
- initializeGtag(config.tagId);
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
- config.googleAds?.tagId &&
12176
- config.googleAds?.conversionId &&
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: config.googleAds.tagId,
12184
- conversionId: config.googleAds.conversionId,
12185
- conversionValue,
12186
- conversionCurrency: config.googleAds.conversionCurrency || "EUR",
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
  }
@@ -15449,6 +15469,13 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15449
15469
  const [shouldRenderInstanceSelection, setShouldRenderInstanceSelection] = useState(false);
15450
15470
  const [shouldRenderUpsells, setShouldRenderUpsells] = useState(false);
15451
15471
  const [shouldRenderBookingForm, setShouldRenderBookingForm] = useState(false);
15472
+ // Google Ads config (received from API, set once from the first API response)
15473
+ const [googleAdsConfig, setGoogleAdsConfig] = useState(null);
15474
+ const extractGoogleAdsConfig = (data) => {
15475
+ if (!googleAdsConfig && data?.googleAdsConfig?.tagId) {
15476
+ setGoogleAdsConfig(data.googleAdsConfig);
15477
+ }
15478
+ };
15452
15479
  // Promo dialog state
15453
15480
  const [showPromoDialog, setShowPromoDialog] = useState(false);
15454
15481
  const [widgetContainerRef, setWidgetContainerRef] = useState(null);
@@ -15532,6 +15559,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15532
15559
  image: resolvedImage,
15533
15560
  };
15534
15561
  setVoucherConfig(mergedConfig);
15562
+ extractGoogleAdsConfig(data);
15535
15563
  setVoucherEventTypes(data.eventTypes || []);
15536
15564
  // Set system config for payment processing
15537
15565
  if (data.paymentProvider) {
@@ -15554,6 +15582,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15554
15582
  setIsLoadingVoucherConfig(false);
15555
15583
  }
15556
15584
  };
15585
+ // Fire widget pageview once when Google Ads config is received from API
15586
+ const pageviewFiredRef = useRef(false);
15587
+ useEffect(() => {
15588
+ if (!pageviewFiredRef.current && googleAdsConfig?.tagId) {
15589
+ pageviewFiredRef.current = true;
15590
+ handleGoogleAdsPageview(googleAdsConfig.tagId);
15591
+ }
15592
+ }, [googleAdsConfig]);
15557
15593
  // Determine initial step and load data
15558
15594
  useEffect(() => {
15559
15595
  const initializeWidget = async () => {
@@ -15767,6 +15803,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15767
15803
  onWidgetLanguage?.(wl);
15768
15804
  onTimezone?.(wl.timezone);
15769
15805
  }
15806
+ extractGoogleAdsConfig(data);
15770
15807
  setEventTypes(data.eventTypes);
15771
15808
  if (isSingleEventTypeMode && data.eventTypes.length === 1) {
15772
15809
  setSelectedEventType(data.eventTypes[0]);
@@ -15803,6 +15840,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15803
15840
  onWidgetLanguage?.(wl);
15804
15841
  onTimezone?.(wl.timezone);
15805
15842
  }
15843
+ extractGoogleAdsConfig(data);
15806
15844
  setUpcomingEvents(data.upcomingEvents || []);
15807
15845
  }
15808
15846
  else {
@@ -15838,6 +15876,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15838
15876
  onWidgetLanguage?.(wl);
15839
15877
  onTimezone?.(wl.timezone);
15840
15878
  }
15879
+ extractGoogleAdsConfig(data);
15841
15880
  setSpecials(data.specials || []);
15842
15881
  }
15843
15882
  else {
@@ -15864,6 +15903,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15864
15903
  onWidgetLanguage?.(wl);
15865
15904
  onTimezone?.(wl.timezone);
15866
15905
  }
15906
+ extractGoogleAdsConfig(data);
15867
15907
  setEventInstances(data.eventInstances);
15868
15908
  if (data.paymentProvider) {
15869
15909
  setSystemConfig({
@@ -15924,6 +15964,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15924
15964
  onWidgetLanguage?.(wl);
15925
15965
  onTimezone?.(wl.timezone);
15926
15966
  }
15967
+ extractGoogleAdsConfig(data);
15927
15968
  setEventDetails(data.eventDetails);
15928
15969
  setSystemConfig({
15929
15970
  paymentProvider: data.paymentProvider,
@@ -15993,6 +16034,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15993
16034
  onWidgetLanguage?.(wl);
15994
16035
  onTimezone?.(wl.timezone);
15995
16036
  }
16037
+ extractGoogleAdsConfig(data);
15996
16038
  return data.upsells || [];
15997
16039
  }
15998
16040
  else {
@@ -16406,7 +16448,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16406
16448
  url.searchParams.delete("mollie_payment_id");
16407
16449
  url.searchParams.delete("mollie_status");
16408
16450
  window.history.replaceState({}, "", url.toString());
16409
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16451
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16410
16452
  }
16411
16453
  if (viewMode === "specials" && showingPreview) {
16412
16454
  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 +16473,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16431
16473
  setShouldRenderBookingForm(false);
16432
16474
  setSelectedUpsells([]);
16433
16475
  setUpsells([]);
16434
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16476
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16435
16477
  }
16436
16478
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
16437
16479
  return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
@@ -16454,7 +16496,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16454
16496
  url.searchParams.delete("mollie_payment_id");
16455
16497
  url.searchParams.delete("mollie_status");
16456
16498
  window.history.replaceState({}, "", url.toString());
16457
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16499
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16458
16500
  }
16459
16501
  if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
16460
16502
  return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
@@ -16500,7 +16542,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16500
16542
  url.searchParams.delete("mollie_payment_id");
16501
16543
  url.searchParams.delete("mollie_status");
16502
16544
  window.history.replaceState({}, "", url.toString());
16503
- }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16545
+ }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16504
16546
  }
16505
16547
  // Cards mode (default) - show event type selection with optional voucher card
16506
16548
  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 +16598,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16556
16598
  url.searchParams.delete("mollie_payment_id");
16557
16599
  url.searchParams.delete("mollie_status");
16558
16600
  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: () => {
16601
+ }, 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
16602
  setIsSuccess(false);
16561
16603
  setVoucherPurchaseResult(null);
16562
16604
  const url = new URL(window.location.href);