import mixpanel from 'mixpanel-browser'; const MIXPANEL_TOKEN = '42eb73c1d4a74b8993e5bbb4f2e01189'; let initialized = false; /** * Telemetry consent (GDPR): default OFF. Nothing is sent to Mixpanel until the * site owner opts in (onboarding wizard / privacy settings). The flag arrives * via the localized payload (`analyticsData.telemetryOptIn`); a missing flag is * treated as opted-out (fail closed). */ let telemetryConsent = false; /** Config kept so a late opt-in (same page, e.g. during onboarding) can init. */ let storedConfig: MixpanelConfig = {}; function canTrack(): boolean { return initialized && telemetryConsent; } /** Whether the site owner has opted in to Mixpanel usage analytics. */ export function isTelemetryConsentEnabled(): boolean { return telemetryConsent; } // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface AnalyticsData { siteDomain?: string; siteUrl?: string; pluginVersion?: string; wordpressVersion?: string; woocommerceActive?: boolean; siteLanguage?: string; adminEmail?: string; installDate?: string; /** Site-owner telemetry opt-in (default false — Mixpanel no-ops without it). */ telemetryOptIn?: boolean; } export interface MixpanelConfig { analyticsData?: AnalyticsData; settings?: { post_types?: string[] }; } // --------------------------------------------------------------------------- // Initialisation — call once per entry-point // --------------------------------------------------------------------------- export function initMixpanel(config: MixpanelConfig = {}): void { storedConfig = config; telemetryConsent = config.analyticsData?.telemetryOptIn === true; // No consent: keep the client uninitialized so every track call is a no-op. if (!telemetryConsent) return; initializeClient(); } /** * Update telemetry consent at runtime (e.g. the onboarding opt-in step). * Enabling initializes the client immediately using the entry-point config. */ export function setTelemetryConsent(enabled: boolean): void { telemetryConsent = enabled; if (enabled && !initialized) { initializeClient(); return; } try { if (initialized) { if (enabled) { mixpanel.opt_in_tracking(); } else { mixpanel.opt_out_tracking(); } } } catch (err) { console.warn('[LLMagnet] Mixpanel consent update failed', err); } } function initializeClient(): void { if (initialized) return; const config = storedConfig; try { // Session replay stays off so the client does not request the Mixpanel replay CDN. mixpanel.init(MIXPANEL_TOKEN, { autocapture: false, record_sessions_percent: 0, api_host: 'https://api-eu.mixpanel.com', persistence: 'localStorage', }); // Server-side consent is authoritative. Clear any persisted browser opt-out // (e.g. after toggling off in Privacy settings, or a prior session). mixpanel.opt_in_tracking(); const { analyticsData } = config; const domain = analyticsData?.siteDomain || window.location.hostname; // Identify user by site domain mixpanel.identify(domain); // Super properties — attached to every event automatically mixpanel.register({ plugin_version: analyticsData?.pluginVersion ?? '', wordpress_version: analyticsData?.wordpressVersion ?? '', woocommerce_active: analyticsData?.woocommerceActive ?? false, site_language: analyticsData?.siteLanguage ?? navigator.language, }); // User profile properties mixpanel.people.set({ $name: domain, plugin_version: analyticsData?.pluginVersion ?? '', wordpress_version: analyticsData?.wordpressVersion ?? '', woocommerce_active: analyticsData?.woocommerceActive ?? false, install_date: analyticsData?.installDate ?? '', }); // Set post_types_count if settings available if (config.settings?.post_types) { mixpanel.people.set({ post_types_count: config.settings.post_types.length }); } initialized = true; } catch (err) { console.warn('[LLMagnet] Mixpanel init failed', err); } } // --------------------------------------------------------------------------- // User property helpers (for lazy updates from components) // --------------------------------------------------------------------------- export function setUserProperty(props: Record): void { if (!canTrack()) return; mixpanel.people.set(props); } export function incrementUserProperty(prop: string, value = 1): void { if (!canTrack()) return; mixpanel.people.increment(prop, value); } // --------------------------------------------------------------------------- // Page views // --------------------------------------------------------------------------- export function trackPageView(pageName: string): void { if (!canTrack()) return; mixpanel.track('Page - Viewed', { page_name: pageName }); } // --------------------------------------------------------------------------- // LLMs.txt generation // --------------------------------------------------------------------------- export function trackLlmsTxtGenerated( success: boolean, source: 'settings' | 'dashboard' | 'onboarding' | 'overview', errorMessage?: string, ): void { if (!canTrack()) return; mixpanel.track('LLMs.txt - Generated', { success, source, ...(errorMessage && { error_message: errorMessage }), }); } // --------------------------------------------------------------------------- // Settings // --------------------------------------------------------------------------- export function trackSettingsSaved( page: 'llms_txt' | 'content', changedFields: string[] = [], ): void { if (!canTrack()) return; mixpanel.track('Settings - Saved', { page, changed_fields: changedFields }); } // --------------------------------------------------------------------------- // Onboarding // --------------------------------------------------------------------------- export function trackOnboardingStepViewed(stepName: string, stepNumber: number): void { if (!canTrack()) return; mixpanel.track('Onboarding - Step Viewed', { step_name: stepName, step_number: stepNumber }); } export function trackOnboardingStepCompleted(stepName: string, stepNumber: number): void { if (!canTrack()) return; mixpanel.track('Onboarding - Step Completed', { step_name: stepName, step_number: stepNumber }); } export function trackOnboardingSkipped(stepName: string): void { if (!canTrack()) return; mixpanel.track('Onboarding - Skipped', { step_name: stepName }); } export function trackOnboardingDismissed(stepsCompleted: number): void { if (!canTrack()) return; mixpanel.track('Onboarding - Dismissed', { steps_completed: stepsCompleted }); } export function trackOnboardingCompleted(): void { if (!canTrack()) return; mixpanel.track('Onboarding - Completed'); setUserProperty({ onboarding_completed: true }); } export function trackOnboardingReopened(): void { if (!canTrack()) return; mixpanel.track('Onboarding - Reopened'); } // --------------------------------------------------------------------------- // Analytics interactions // --------------------------------------------------------------------------- export function trackTimeRangeChanged(range: string, page: string): void { if (!canTrack()) return; mixpanel.track('Analytics - Time Range Changed', { range, page }); } export function trackViewModeChanged(mode: 'table' | 'cards'): void { if (!canTrack()) return; mixpanel.track('Analytics - View Mode Changed', { mode }); } export function trackBotDetailsViewed(botName: string): void { if (!canTrack()) return; mixpanel.track('Analytics - Bot Details Viewed', { bot_name: botName }); } export function trackLoadMore(page: string, currentCount: number): void { if (!canTrack()) return; mixpanel.track('Analytics - Load More', { page, current_count: currentCount }); } // --------------------------------------------------------------------------- // Products // --------------------------------------------------------------------------- export function trackProductDrawerOpened(productId: number, productName: string): void { if (!canTrack()) return; mixpanel.track('Products - Drawer Opened', { product_id: productId, product_name: productName }); } // --------------------------------------------------------------------------- // Pages // --------------------------------------------------------------------------- export function trackPageDrawerOpened(postId: number, pageTitle: string): void { if (!canTrack()) return; mixpanel.track('Pages - Drawer Opened', { post_id: postId, page_title: pageTitle }); } export function trackPageDetailsSaved(postId: number): void { if (!canTrack()) return; mixpanel.track('Pages - Details Saved', { post_id: postId }); } // --------------------------------------------------------------------------- // Content actions // --------------------------------------------------------------------------- export function trackCopyPrompt(): void { if (!canTrack()) return; mixpanel.track('Content - Copy Prompt'); } export function trackFixRobotsTxt(success: boolean): void { if (!canTrack()) return; mixpanel.track('Content - Fix Robots.txt', { success }); } export function trackImageSelected(hasImage: boolean): void { if (!canTrack()) return; mixpanel.track('Content - Image Selected', { has_image: hasImage }); } export function trackAltTextUpdated(imageId: number): void { if (!canTrack()) return; mixpanel.track('Content - Alt Text Updated', { image_id: imageId }); } export function trackVisibilityScoreRefreshed(): void { if (!canTrack()) return; mixpanel.track('Visibility Score - Refreshed'); } // --------------------------------------------------------------------------- // Reports // --------------------------------------------------------------------------- export function trackReportSettingsSaved( frequency: string, template: string, hasLogo: boolean, ): void { if (!canTrack()) return; mixpanel.track('Reports - Settings Saved', { frequency, template, has_logo: hasLogo }); } export function trackReportTestSent(success: boolean): void { if (!canTrack()) return; mixpanel.track('Reports - Test Sent', { success }); } // --------------------------------------------------------------------------- // News Feed // --------------------------------------------------------------------------- export function trackNewsFeedOpened(itemCount: number, feedVersion: string): void { if (!canTrack()) return; mixpanel.track('News Feed - Opened', { item_count: itemCount, feed_version: feedVersion }); } export function trackNewsFeedClosed( closeMethod: 'backdrop' | 'button' | 'escape', feedVersion: string, ): void { if (!canTrack()) return; mixpanel.track('News Feed - Closed', { close_method: closeMethod, feed_version: feedVersion }); } export function trackNewsFeedCtaClicked( itemId: string, itemTitle: string, ctaText: string, ctaUrl: string, isInternal: boolean, ): void { if (!canTrack()) return; mixpanel.track('News Feed - CTA Clicked', { item_id: itemId, item_title: itemTitle, cta_text: ctaText, cta_url: ctaUrl, is_internal: isInternal, }); } export function trackNewsFeedLinkClicked( itemId: string, itemTitle: string, linkText: string, linkUrl: string, isInternal: boolean, ): void { if (!canTrack()) return; mixpanel.track('News Feed - Link Clicked', { item_id: itemId, item_title: itemTitle, link_text: linkText, link_url: linkUrl, is_internal: isInternal, }); } export function trackNewsFeedScrolled(depthPercent: number, feedVersion: string): void { if (!canTrack()) return; mixpanel.track('News Feed - Scrolled', { scroll_depth_pct: depthPercent, feed_version: feedVersion }); } // --------------------------------------------------------------------------- // Promo Modal (centered popup) // --------------------------------------------------------------------------- export function trackPromoModalViewed(modalId: string, displayCount: number): void { if (!canTrack()) return; mixpanel.track('Promo Modal - Viewed', { modal_id: modalId, display_count: displayCount }); } export function trackPromoModalClosed( modalId: string, closeMethod: 'backdrop' | 'x' | 'secondary' | 'escape', ): void { if (!canTrack()) return; mixpanel.track('Promo Modal - Closed', { modal_id: modalId, close_method: closeMethod }); } export function trackPromoModalCtaClicked( modalId: string, ctaType: 'primary' | 'secondary', ctaLabel: string, ctaUrl: string, isInternal: boolean, ): void { if (!canTrack()) return; mixpanel.track('Promo Modal - CTA Clicked', { modal_id: modalId, cta_type: ctaType, cta_label: ctaLabel, cta_url: ctaUrl, is_internal: isInternal, }); } // --------------------------------------------------------------------------- // Sidebar navigation // --------------------------------------------------------------------------- export function trackSidebarToggled(action: 'expanded' | 'collapsed'): void { if (!canTrack()) return; mixpanel.track('Sidebar - Toggled', { action }); } export function trackSidebarNavClicked( destinationPage: string, fromPage: string, sidebarState: 'collapsed' | 'expanded', ): void { if (!canTrack()) return; mixpanel.track('Sidebar - Nav Item Clicked', { destination_page: destinationPage, from_page: fromPage, sidebar_state: sidebarState, }); } // --------------------------------------------------------------------------- // Feedback / bugs // --------------------------------------------------------------------------- export function trackFeedbackOpened(fromPage: string, sidebarState: 'collapsed' | 'expanded'): void { if (!canTrack()) return; mixpanel.track('Feedback - Opened', { from_page: fromPage, sidebar_state: sidebarState }); } export function trackFeedbackSubmitted(subjectLength: number, messageLength: number): void { if (!canTrack()) return; mixpanel.track('Feedback - Submitted', { subject_length: subjectLength, message_length: messageLength, }); } export function trackFeedbackSubmitFailed(httpStatus: number, errorCode?: string): void { if (!canTrack()) return; mixpanel.track('Feedback - Submit Failed', { http_status: httpStatus, ...(errorCode ? { error_code: errorCode } : {}), }); } export function trackFeedbackClosed( closeMethod: 'backdrop' | 'x' | 'cancel' | 'escape', ): void { if (!canTrack()) return; mixpanel.track('Feedback - Closed', { close_method: closeMethod }); } export function trackOverviewStarRating(rating: number, onSent?: () => void): void { if (!canTrack()) { onSent?.(); return; } mixpanel.track('Overview - Star Rating', { rating }, () => { setUserProperty({ overview_star_rating: rating }); onSent?.(); }); } // --------------------------------------------------------------------------- // Errors // --------------------------------------------------------------------------- export function trackError(context: string, errorMessage: string): void { if (!canTrack()) return; mixpanel.track('Error - Occurred', { context, error_message: errorMessage }); } export { mixpanel };