initial commit
This commit is contained in:
310
lib/analytics.tsx
Normal file
310
lib/analytics.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 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
|
||||
Reference in New Issue
Block a user