import { useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; // Analytics event types interface AnalyticsEvent { type: 'pageview' | 'feature_usage' | 'performance' | 'error'; timestamp: number; data: Record; } // Simple in-memory analytics storage const ANALYTICS_KEY = 'mockupaws_analytics'; const MAX_EVENTS = 1000; class AnalyticsService { private events: AnalyticsEvent[] = []; private userId: string | null = null; private sessionId: string; constructor() { this.sessionId = this.generateSessionId(); this.loadEvents(); this.trackSessionStart(); } private generateSessionId(): string { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } private loadEvents() { try { const stored = localStorage.getItem(ANALYTICS_KEY); if (stored) { this.events = JSON.parse(stored); } } catch { this.events = []; } } private saveEvents() { try { // Keep only recent events const recentEvents = this.events.slice(-MAX_EVENTS); localStorage.setItem(ANALYTICS_KEY, JSON.stringify(recentEvents)); } catch { // Storage might be full, clear old events this.events = this.events.slice(-100); try { localStorage.setItem(ANALYTICS_KEY, JSON.stringify(this.events)); } catch { // Give up } } } setUserId(userId: string | null) { this.userId = userId; } private trackEvent(type: AnalyticsEvent['type'], data: Record) { const event: AnalyticsEvent = { type, timestamp: Date.now(), data: { ...data, sessionId: this.sessionId, userId: this.userId, }, }; this.events.push(event); this.saveEvents(); // Send to backend if available (batch processing) this.sendToBackend(event); } private async sendToBackend(event: AnalyticsEvent) { // In production, you'd batch these and send periodically // For now, we'll just log in development if (import.meta.env.DEV) { console.log('[Analytics]', event); } } private trackSessionStart() { this.trackEvent('feature_usage', { feature: 'session_start', userAgent: navigator.userAgent, language: navigator.language, screenSize: `${window.screen.width}x${window.screen.height}`, }); } trackPageView(path: string) { this.trackEvent('pageview', { path, referrer: document.referrer, }); } trackFeatureUsage(feature: string, details?: Record) { this.trackEvent('feature_usage', { feature, ...details, }); } trackPerformance(metric: string, value: number, details?: Record) { this.trackEvent('performance', { metric, value, ...details, }); } trackError(error: Error, context?: Record) { this.trackEvent('error', { message: error.message, stack: error.stack, ...context, }); } // Get analytics data for dashboard getAnalyticsData() { const now = Date.now(); const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000; const recentEvents = this.events.filter((e) => e.timestamp > thirtyDaysAgo); // Calculate MAU (Monthly Active Users - unique sessions in last 30 days) const uniqueSessions30d = new Set( recentEvents.map((e) => e.data.sessionId as string) ).size; // Daily active users (last 7 days) const dailyActiveUsers = this.calculateDailyActiveUsers(recentEvents, 7); // Feature adoption const featureUsage = this.calculateFeatureUsage(recentEvents); // Page views const pageViews = this.calculatePageViews(recentEvents); // Performance metrics const performanceMetrics = this.calculatePerformanceMetrics(recentEvents); // Cost predictions const costPredictions = this.generateCostPredictions(); return { mau: uniqueSessions30d, dailyActiveUsers, featureUsage, pageViews, performanceMetrics, costPredictions, totalEvents: this.events.length, }; } private calculateDailyActiveUsers(events: AnalyticsEvent[], days: number) { const dailyUsers: { date: string; users: number }[] = []; const now = Date.now(); for (let i = days - 1; i >= 0; i--) { const date = new Date(now - i * 24 * 60 * 60 * 1000); const dateStr = date.toISOString().split('T')[0]; const dayStart = date.setHours(0, 0, 0, 0); const dayEnd = dayStart + 24 * 60 * 60 * 1000; const dayEvents = events.filter( (e) => e.timestamp >= dayStart && e.timestamp < dayEnd ); const uniqueUsers = new Set(dayEvents.map((e) => e.data.sessionId as string)).size; dailyUsers.push({ date: dateStr, users: uniqueUsers }); } return dailyUsers; } private calculateFeatureUsage(events: AnalyticsEvent[]) { const featureCounts: Record = {}; events .filter((e) => e.type === 'feature_usage') .forEach((e) => { const feature = e.data.feature as string; featureCounts[feature] = (featureCounts[feature] || 0) + 1; }); return Object.entries(featureCounts) .map(([feature, count]) => ({ feature, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); } private calculatePageViews(events: AnalyticsEvent[]) { const pageCounts: Record = {}; events .filter((e) => e.type === 'pageview') .forEach((e) => { const path = e.data.path as string; pageCounts[path] = (pageCounts[path] || 0) + 1; }); return Object.entries(pageCounts) .map(([path, count]) => ({ path, count })) .sort((a, b) => b.count - a.count); } private calculatePerformanceMetrics(events: AnalyticsEvent[]) { const metrics: Record = {}; events .filter((e) => e.type === 'performance') .forEach((e) => { const metric = e.data.metric as string; const value = e.data.value as number; if (!metrics[metric]) { metrics[metric] = []; } metrics[metric].push(value); }); return Object.entries(metrics).map(([metric, values]) => ({ metric, avg: values.reduce((a, b) => a + b, 0) / values.length, min: Math.min(...values), max: Math.max(...values), count: values.length, })); } private generateCostPredictions() { // Simple trend analysis for cost predictions // In a real app, this would use actual historical cost data const currentMonth = 1000; const trend = 0.05; // 5% growth const predictions = []; for (let i = 1; i <= 3; i++) { const predicted = currentMonth * Math.pow(1 + trend, i); const confidence = Math.max(0.7, 1 - i * 0.1); // Decreasing confidence predictions.push({ month: i, predicted, confidenceLow: predicted * (1 - (1 - confidence)), confidenceHigh: predicted * (1 + (1 - confidence)), }); } return predictions; } // Detect anomalies in cost data detectAnomalies(costData: number[]) { if (costData.length < 7) return []; const avg = costData.reduce((a, b) => a + b, 0) / costData.length; const stdDev = Math.sqrt( costData.reduce((sq, n) => sq + Math.pow(n - avg, 2), 0) / costData.length ); const threshold = 2; // 2 standard deviations return costData .map((cost, index) => { const zScore = Math.abs((cost - avg) / stdDev); if (zScore > threshold) { return { index, cost, zScore, type: cost > avg ? 'spike' : 'drop', }; } return null; }) .filter((a): a is NonNullable => a !== null); } } // Singleton instance export const analytics = new AnalyticsService(); // React hook for page view tracking export function usePageViewTracking() { const location = useLocation(); useEffect(() => { analytics.trackPageView(location.pathname); }, [location.pathname]); } // React hook for feature tracking export function useFeatureTracking() { return useCallback((feature: string, details?: Record) => { analytics.trackFeatureUsage(feature, details); }, []); } // Performance observer hook export function usePerformanceTracking() { useEffect(() => { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.entryType === 'measure') { analytics.trackPerformance(entry.name, entry.duration || 0, { entryType: entry.entryType, }); } } }); try { observer.observe({ entryTypes: ['measure', 'navigation'] }); } catch { // Some entry types may not be supported } return () => observer.disconnect(); } }, []); }