UNPKG

@akson/cortex-landing-hooks

Version:

React hooks for landing pages - device detection, API calls, form submission, analytics, and performance

584 lines (579 loc) 17.4 kB
import { __require } from "./chunk-Y6FXYEAI.mjs"; // src/analytics/useAnalytics.ts import { useCallback, useEffect, useMemo, useState } from "react"; function useAnalytics(config = {}) { const { gtmId, posthogKey, facebookPixelId, customTracker, debug = false } = config; const [isReady, setIsReady] = useState(false); const [sessionId] = useState(() => Math.random().toString(36).substring(2, 15)); useEffect(() => { if (typeof window === "undefined") return; const checkReady = () => { const gtmReady = !gtmId || typeof window.gtag === "function"; const posthogReady = !posthogKey || typeof window.posthog === "object"; const facebookReady = !facebookPixelId || typeof window.fbq === "function"; const allReady = gtmReady && posthogReady && facebookReady; if (allReady !== isReady) { setIsReady(allReady); } }; checkReady(); const interval = setInterval(checkReady, 1e3); const timeout = setTimeout(() => { clearInterval(interval); setIsReady(true); }, 1e4); return () => { clearInterval(interval); clearTimeout(timeout); }; }, [gtmId, posthogKey, facebookPixelId, isReady]); const debugLog = useCallback((message, data) => { if (debug) { console.log(`[Analytics] ${message}`, data || ""); } }, [debug]); const track = useCallback((eventName, properties = {}) => { if (typeof window === "undefined") return; const eventData = { ...properties, sessionId, timestamp: (/* @__PURE__ */ new Date()).toISOString() }; debugLog(`Tracking event: ${eventName}`, eventData); if (gtmId && typeof window.gtag === "function") { window.gtag("event", eventName, eventData); } if (posthogKey && typeof window.posthog === "object") { window.posthog.capture(eventName, eventData); } if (facebookPixelId && typeof window.fbq === "function") { window.fbq("track", eventName, eventData); } if (customTracker) { customTracker({ name: eventName, properties: eventData }); } }, [gtmId, posthogKey, facebookPixelId, customTracker, sessionId, debugLog]); const trackPageView = useCallback((path, properties = {}) => { const currentPath = path || (typeof window !== "undefined" ? window.location.pathname : ""); track("page_view", { ...properties, page_path: currentPath, page_title: typeof window !== "undefined" ? document.title : "", page_location: typeof window !== "undefined" ? window.location.href : "" }); }, [track]); const trackInteraction = useCallback((element, action, properties = {}) => { track("interaction", { ...properties, element, action }); }, [track]); const trackConversion = useCallback((type, value, properties = {}) => { track("conversion", { ...properties, conversion_type: type, value: value || 0, currency: properties.currency || "USD" }); }, [track]); const identify = useCallback((userData) => { if (typeof window === "undefined") return; debugLog("Identifying user", userData); if (posthogKey && typeof window.posthog === "object") { window.posthog.identify(userData.userId, userData); } if (typeof window !== "undefined") { window.userData = userData; } }, [posthogKey, debugLog]); const collectFormData = useCallback(() => { if (typeof window === "undefined") return null; const forms = document.querySelectorAll("form"); if (forms.length === 0) return null; let totalFields = 0; const filledFields = []; const emptyFields = []; forms.forEach((form) => { const inputs = form.querySelectorAll("input, textarea, select"); inputs.forEach((input) => { const field = input; if (field.type === "hidden" || field.type === "submit" || field.type === "button") { return; } const fieldName = field.getAttribute("name") || field.getAttribute("id") || field.getAttribute("placeholder") || "unnamed_field"; totalFields++; const hasValue = field instanceof HTMLInputElement && (field.type === "checkbox" || field.type === "radio") ? field.checked : Boolean(field.value && field.value.trim() !== ""); if (hasValue) { filledFields.push(fieldName); } else { emptyFields.push(fieldName); } }); }); if (totalFields === 0) return null; return { completionPercentage: Math.round(filledFields.length / totalFields * 100), fieldsCompleted: filledFields.length, totalFields, filledFields, emptyFields }; }, []); const trackFormStart = useCallback((formName, properties = {}) => { track("form_start", { ...properties, form_name: formName }); }, [track]); const trackFormComplete = useCallback((formName, properties = {}) => { const formData = collectFormData(); track("form_complete", { ...properties, form_name: formName, ...formData }); }, [track, collectFormData]); const trackFormAbandonment = useCallback((formName, step, properties = {}) => { const formData = collectFormData(); track("form_abandonment", { ...properties, form_name: formName, abandonment_step: step, ...formData }); }, [track, collectFormData]); const getSessionId = useCallback(() => sessionId, [sessionId]); return useMemo(() => ({ // Core tracking track, trackPageView, trackInteraction, trackConversion, // User identification identify, // Form tracking collectFormData, trackFormStart, trackFormComplete, trackFormAbandonment, // Utility getSessionId, isReady, debug: debugLog }), [ track, trackPageView, trackInteraction, trackConversion, identify, collectFormData, trackFormStart, trackFormComplete, trackFormAbandonment, getSessionId, isReady, debugLog ]); } function useEcommerceAnalytics(config = {}) { const analytics = useAnalytics(config); const trackPurchase = useCallback((transactionId, items, value, currency = "USD") => { analytics.track("purchase", { transaction_id: transactionId, value, currency, items }); }, [analytics]); const trackAddToCart = useCallback((item, value, currency = "USD") => { analytics.track("add_to_cart", { ...item, value: value || item.price || 0, currency }); }, [analytics]); const trackViewItem = useCallback((item, value, currency = "USD") => { analytics.track("view_item", { ...item, value: value || item.price || 0, currency }); }, [analytics]); const trackBeginCheckout = useCallback((items, value, currency = "USD") => { analytics.track("begin_checkout", { value, currency, items }); }, [analytics]); return { ...analytics, trackPurchase, trackAddToCart, trackViewItem, trackBeginCheckout }; } // src/analytics/useAnalyticsCompat.ts import { useCallback as useCallback2, useMemo as useMemo2 } from "react"; var FUNNEL_STAGES; var calculateConversionValue; try { const analyticsV2 = __require("@/app/lib/analytics-v2"); FUNNEL_STAGES = analyticsV2.FUNNEL_STAGES; calculateConversionValue = analyticsV2.calculateConversionValue; } catch { FUNNEL_STAGES = {}; calculateConversionValue = () => 0; } var mockAnalytics = { track: (event, properties) => { console.log("Analytics track:", event, properties); }, trackPageView: (properties) => { console.log("Analytics page view:", properties); }, trackInteraction: (properties) => { console.log("Analytics interaction:", properties); }, trackEcommerce: (properties) => { console.log("Analytics ecommerce:", properties); }, trackExperiment: (properties) => { console.log("Analytics experiment:", properties); }, trackFeatureFlag: (properties) => { console.log("Analytics feature flag:", properties); }, identify: (userId, properties) => { console.log("Analytics identify:", userId, properties); }, getSessionId: () => `session_${Date.now()}`, getFunnelPath: () => [], getTimeInFunnel: () => 0, clearSession: () => console.log("Session cleared"), isReady: true }; var mockFunnel = { trackStage: (stage, data) => { console.log("Funnel stage:", stage, data); }, trackAbandonment: (stage, reason, data) => { console.log("Funnel abandonment:", stage, reason, data); }, trackConversion: (type, value, data) => { console.log("Funnel conversion:", type, value, data); } }; var mockLead = { sendUserData: (userData) => { console.log("Lead user data:", userData); } }; function useAnalyticsCompat() { const analytics = mockAnalytics; const funnel = mockFunnel; const lead = mockLead; const trackStage = useCallback2((stage, data) => { const conversionValue = calculateConversionValue ? calculateConversionValue(stage, data) : 0; const standardizedEvent = FUNNEL_STAGES ? Object.values(FUNNEL_STAGES).find((s) => s === stage) : null; if (standardizedEvent && typeof standardizedEvent === "string") { analytics.track(standardizedEvent, { ...data, value: conversionValue, funnel_stage: stage }); } else { funnel.trackStage(stage, data); } }, [analytics, funnel]); const trackAbandonment = useCallback2((lastStage, reason, additionalData) => { funnel.trackAbandonment(lastStage, reason, additionalData); }, [funnel]); const trackConversion = useCallback2((type, value, additionalData) => { funnel.trackConversion(type, value, additionalData); }, [funnel]); const collectFormData = useCallback2(() => { if (typeof window === "undefined") return null; const forms = document.querySelectorAll("form"); if (forms.length === 0) return null; let totalFields = 0; const filledFields = []; const emptyFields = []; forms.forEach((form) => { const inputs = form.querySelectorAll("input, textarea, select"); inputs.forEach((input) => { const field = input; if (field.type === "hidden" || field.type === "submit" || field.type === "button") { return; } const fieldName = field.getAttribute("name") || field.getAttribute("id") || field.getAttribute("placeholder") || "unnamed_field"; totalFields++; const hasValue = field instanceof HTMLInputElement && (field.type === "checkbox" || field.type === "radio") ? field.checked : Boolean(field.value && field.value.trim() !== ""); if (hasValue) { filledFields.push(fieldName); } else { emptyFields.push(fieldName); } }); }); if (totalFields === 0) return null; return { completionPercentage: Math.round(filledFields.length / totalFields * 100), fieldsCompleted: filledFields.length, totalFields, filledFields, emptyFields }; }, []); return useMemo2(() => ({ // Funnel tracking trackStage, trackAbandonment, trackConversion, // Enhanced Conversions sendUserData: lead.sendUserData, // Core tracking from @akson track: analytics.track, trackPageView: analytics.trackPageView, trackInteraction: analytics.trackInteraction, trackEcommerce: analytics.trackEcommerce, trackExperiment: analytics.trackExperiment, trackFeatureFlag: analytics.trackFeatureFlag, identify: analytics.identify, // Utility functions getSessionId: analytics.getSessionId, getFunnelPath: analytics.getFunnelPath, getTimeInFunnel: analytics.getTimeInFunnel, clearFunnelState: analytics.clearSession, collectFormData, // Status checks isGTMReady: () => analytics.isReady, getPostHog: () => typeof window !== "undefined" ? window.posthog : null }), [ trackStage, trackAbandonment, trackConversion, lead.sendUserData, analytics, collectFormData ]); } var useAnalytics2 = useAnalyticsCompat; // src/analytics/useGTMTracking.ts import { useCallback as useCallback3, useEffect as useEffect2 } from "react"; var DEFAULT_CONFIG = { enabled: true, debug: process.env.NODE_ENV === "development", logEvents: process.env.NODE_ENV === "development" }; var GTM_EVENTS = { INTERACTION: "interaction", CONVERSION: "conversion", PAGEVIEW: "page_view", EXPERIMENT: "experiment" }; var GTM_ACTIONS = { CLICK: "click", VIEW: "view", SUBMIT: "submit", HOVER: "hover", FOCUS: "focus", BLUR: "blur", START: "start", STEP: "step", COMPLETE: "complete", ABANDON: "abandon", ADD_TO_CART: "add_to_cart", CONVERT: "convert", SCROLL: "scroll" }; var GTM_COMPONENTS = { HERO: "hero", CTA: "cta", FORM: "form", WHATSAPP: "whatsapp", PRODUCT: "product", NAVIGATION: "navigation", PAGE: "page" }; function useGTMTracking(config = {}) { const finalConfig = { ...DEFAULT_CONFIG, ...config }; useEffect2(() => { if (typeof window !== "undefined") { window.dataLayer = window.dataLayer || []; } }, []); const track = useCallback3( (event) => { if (typeof window === "undefined") return; if (!finalConfig.enabled) { if (finalConfig.debug) { console.log("[GTM] Tracking disabled, skipping event:", event); } return; } if (!event.event) { console.warn('[GTM] Event missing required "event" field:', event); return; } const enrichedEvent = { ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString(), url: window.location.href, user_agent: navigator.userAgent, viewport: `${window.innerWidth}x${window.innerHeight}` }; window.dataLayer = window.dataLayer || []; window.dataLayer.push(enrichedEvent); if (finalConfig.logEvents) { console.log("[GTM] Event tracked:", enrichedEvent); } }, [finalConfig] ); const trackInteraction = useCallback3( (component, action, metadata) => { track({ event: GTM_EVENTS.INTERACTION, component, action, metadata }); }, [track] ); const trackConversion = useCallback3( (component, value, metadata) => { track({ event: GTM_EVENTS.CONVERSION, component, value, metadata }); }, [track] ); const trackPageView = useCallback3( (metadata) => { track({ event: GTM_EVENTS.PAGEVIEW, metadata }); }, [track] ); const trackExperiment = useCallback3( (experimentName, variant, action, metadata) => { track({ event: GTM_EVENTS.EXPERIMENT, action, metadata: { experiment_name: experimentName, variant, ...metadata } }); }, [track] ); const trackWhatsApp = useCallback3( (action = GTM_ACTIONS.CLICK, metadata) => { trackInteraction(GTM_COMPONENTS.WHATSAPP, action, metadata); }, [trackInteraction] ); const trackForm = useCallback3( (action, formType, metadata) => { trackInteraction(GTM_COMPONENTS.FORM, action, { form_type: formType, ...metadata }); }, [trackInteraction] ); const trackCTA = useCallback3( (action = GTM_ACTIONS.CLICK, metadata) => { trackInteraction(GTM_COMPONENTS.CTA, action, metadata); }, [trackInteraction] ); const trackHero = useCallback3( (action = GTM_ACTIONS.VIEW, metadata) => { trackInteraction(GTM_COMPONENTS.HERO, action, metadata); }, [trackInteraction] ); const trackProduct = useCallback3( (action, productId, productName, metadata) => { trackInteraction(GTM_COMPONENTS.PRODUCT, action, { product_id: productId, product_name: productName, ...metadata }); }, [trackInteraction] ); const identify = useCallback3( (userId, traits) => { if (typeof window === "undefined") return; window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "identify", userId, userProperties: traits }); if (finalConfig.debug) { console.log("[GTM] User identified:", userId, traits); } }, [finalConfig.debug] ); const reset = useCallback3(() => { if (typeof window === "undefined") return; sessionStorage.removeItem("gtm_session_id"); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "reset", timestamp: (/* @__PURE__ */ new Date()).toISOString() }); if (finalConfig.debug) { console.log("[GTM] Session reset"); } }, [finalConfig.debug]); return { // Core function track, // Specific tracking functions trackInteraction, trackConversion, trackPageView, trackExperiment, // Component-specific functions trackWhatsApp, trackForm, trackCTA, trackHero, trackProduct, // User management identify, reset, // Constants for components to use ACTIONS: GTM_ACTIONS, COMPONENTS: GTM_COMPONENTS, EVENTS: GTM_EVENTS }; } export { useAnalytics, useEcommerceAnalytics, useAnalyticsCompat, useAnalytics2, GTM_EVENTS, GTM_ACTIONS, GTM_COMPONENTS, useGTMTracking };