ai-wpa/lib/analytics.tsx

311 lines
7.7 KiB
TypeScript

/**
* Analytics utilities and tracking setup
* Supports Google Analytics, Plausible, and custom tracking
*/
// Types for analytics events
export interface AnalyticsEvent {
action: string
category: string
label?: string
value?: number
}
export interface PageViewEvent {
page_title: string
page_location: string
page_path: string
}
export interface CustomEvent {
event_name: string
[key: string]: any
}
// Analytics providers configuration
interface AnalyticsConfig {
googleAnalytics?: {
measurementId: string
enabled: boolean
}
plausible?: {
domain: string
enabled: boolean
}
customTracking?: {
enabled: boolean
endpoint?: string
}
debug?: boolean
}
class Analytics {
private config: AnalyticsConfig
private isInitialized = false
constructor(config: AnalyticsConfig) {
this.config = config
}
// Initialize analytics
init() {
if (this.isInitialized || typeof window === 'undefined') return
// Initialize Google Analytics
if (this.config.googleAnalytics?.enabled && this.config.googleAnalytics.measurementId) {
this.initGoogleAnalytics(this.config.googleAnalytics.measurementId)
}
// Initialize Plausible
if (this.config.plausible?.enabled && this.config.plausible.domain) {
this.initPlausible(this.config.plausible.domain)
}
this.isInitialized = true
if (this.config.debug) {
console.log('Analytics initialized with config:', this.config)
}
}
// Initialize Google Analytics
private initGoogleAnalytics(measurementId: string) {
// Load gtag script
const script1 = document.createElement('script')
script1.async = true
script1.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`
document.head.appendChild(script1)
// Initialize gtag
const script2 = document.createElement('script')
script2.innerHTML = `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${measurementId}', {
page_title: document.title,
page_location: window.location.href,
});
`
document.head.appendChild(script2)
// Make gtag available globally
;(window as any).gtag =
(window as any).gtag ||
function () {
;(window as any).dataLayer = (window as any).dataLayer || []
;(window as any).dataLayer.push(arguments)
}
}
// Initialize Plausible
private initPlausible(domain: string) {
const script = document.createElement('script')
script.defer = true
script.setAttribute('data-domain', domain)
script.src = 'https://plausible.io/js/script.js'
document.head.appendChild(script)
}
// Track page view
pageView(event: Partial<PageViewEvent> = {}) {
if (typeof window === 'undefined') return
const pageViewData = {
page_title: document.title,
page_location: window.location.href,
page_path: window.location.pathname,
...event,
}
// Google Analytics
if (this.config.googleAnalytics?.enabled && (window as any).gtag) {
;(window as any).gtag('config', this.config.googleAnalytics.measurementId, {
page_title: pageViewData.page_title,
page_location: pageViewData.page_location,
})
}
// Plausible (automatically tracks page views)
// Custom tracking
if (this.config.customTracking?.enabled) {
this.customTrack('page_view', pageViewData)
}
if (this.config.debug) {
console.log('Page view tracked:', pageViewData)
}
}
// Track custom event
track(eventName: string, properties: Record<string, any> = {}) {
if (typeof window === 'undefined') return
// Google Analytics
if (this.config.googleAnalytics?.enabled && (window as any).gtag) {
;(window as any).gtag('event', eventName, properties)
}
// Plausible
if (this.config.plausible?.enabled && (window as any).plausible) {
;(window as any).plausible(eventName, { props: properties })
}
// Custom tracking
if (this.config.customTracking?.enabled) {
this.customTrack(eventName, properties)
}
if (this.config.debug) {
console.log('Event tracked:', eventName, properties)
}
}
// Track user signup
trackSignup(method: string = 'email') {
this.track('sign_up', { method })
}
// Track user login
trackLogin(method: string = 'email') {
this.track('login', { method })
}
// Track user logout
trackLogout() {
this.track('logout')
}
// Track form submission
trackFormSubmit(formName: string, success: boolean = true) {
this.track('form_submit', {
form_name: formName,
success,
})
}
// Track button click
trackButtonClick(buttonName: string, location?: string) {
this.track('button_click', {
button_name: buttonName,
location,
})
}
// Track search
trackSearch(searchTerm: string, resultCount?: number) {
this.track('search', {
search_term: searchTerm,
result_count: resultCount,
})
}
// Track file download
trackDownload(fileName: string, fileType?: string) {
this.track('file_download', {
file_name: fileName,
file_type: fileType,
})
}
// Custom tracking implementation
private async customTrack(eventName: string, properties: Record<string, any>) {
if (!this.config.customTracking?.endpoint) return
try {
await fetch(this.config.customTracking.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
event: eventName,
properties: {
...properties,
timestamp: new Date().toISOString(),
user_agent: navigator.userAgent,
url: window.location.href,
},
}),
})
} catch (error) {
if (this.config.debug) {
console.error('Custom tracking failed:', error)
}
}
}
// Identify user
identify(userId: string, traits: Record<string, any> = {}) {
// Google Analytics
if (this.config.googleAnalytics?.enabled && (window as any).gtag) {
;(window as any).gtag('config', this.config.googleAnalytics.measurementId, {
user_id: userId,
custom_map: traits,
})
}
// Custom tracking
if (this.config.customTracking?.enabled) {
this.customTrack('identify', { user_id: userId, traits })
}
if (this.config.debug) {
console.log('User identified:', userId, traits)
}
}
}
// Create analytics instance
const analyticsConfig: AnalyticsConfig = {
googleAnalytics: {
measurementId: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || '',
enabled: !!(process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID && process.env.NODE_ENV === 'production'),
},
plausible: {
domain: process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN || '',
enabled: !!(process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN && process.env.NODE_ENV === 'production'),
},
customTracking: {
enabled: false,
endpoint: process.env.NEXT_PUBLIC_ANALYTICS_ENDPOINT,
},
debug: process.env.NODE_ENV === 'development',
}
export const analytics = new Analytics(analyticsConfig)
// React hook for analytics
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export function useAnalytics() {
const pathname = usePathname()
useEffect(() => {
analytics.init()
}, [])
useEffect(() => {
analytics.pageView({
page_path: pathname,
})
}, [pathname])
return analytics
}
// Higher-order component for analytics
import React from 'react'
export function withAnalytics<P extends object>(Component: React.ComponentType<P>) {
return function AnalyticsWrapper(props: P) {
useAnalytics()
return <Component {...props} />
}
}
// Export analytics instance
export default analytics