✅ Resolved: - Removed unused imports (Database, BarChart3, Filter, etc.) - Fixed TypeScript 'any' types to proper types - Removed unused variables and parameters - Cleaned up import statements 🎯 Results: - ESLint errors: 0 ❌ → ✅ - Only 2 non-critical warnings remain (img vs Image) - Code is now production-ready for CI/CD 📊 Performance: - Type safety improved - Bundle size optimized through tree-shaking - Better developer experience
186 lines
5.1 KiB
TypeScript
186 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import { trackWebVitals, trackPerformance } from './analytics';
|
|
|
|
// Web Vitals types
|
|
interface Metric {
|
|
name: string;
|
|
value: number;
|
|
delta: number;
|
|
id: string;
|
|
}
|
|
|
|
// Simple Web Vitals implementation (since we don't want to add external dependencies)
|
|
const getCLS = (onPerfEntry: (metric: Metric) => void) => {
|
|
let clsValue = 0;
|
|
let sessionValue = 0;
|
|
let sessionEntries: PerformanceEntry[] = [];
|
|
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (!(entry as PerformanceEntry & { hadRecentInput?: boolean }).hadRecentInput) {
|
|
const firstSessionEntry = sessionEntries[0];
|
|
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
|
|
|
|
if (sessionValue && entry.startTime - lastSessionEntry.startTime < 1000 && entry.startTime - firstSessionEntry.startTime < 5000) {
|
|
sessionValue += (entry as PerformanceEntry & { value?: number }).value || 0;
|
|
sessionEntries.push(entry);
|
|
} else {
|
|
sessionValue = (entry as PerformanceEntry & { value?: number }).value || 0;
|
|
sessionEntries = [entry];
|
|
}
|
|
|
|
if (sessionValue > clsValue) {
|
|
clsValue = sessionValue;
|
|
onPerfEntry({
|
|
name: 'CLS',
|
|
value: clsValue,
|
|
delta: clsValue,
|
|
id: `cls-${Date.now()}`,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'layout-shift', buffered: true });
|
|
};
|
|
|
|
const getFID = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
onPerfEntry({
|
|
name: 'FID',
|
|
value: (entry as PerformanceEntry & { processingStart?: number }).processingStart! - entry.startTime,
|
|
delta: (entry as PerformanceEntry & { processingStart?: number }).processingStart! - entry.startTime,
|
|
id: `fid-${Date.now()}`,
|
|
});
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'first-input', buffered: true });
|
|
};
|
|
|
|
const getFCP = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (entry.name === 'first-contentful-paint') {
|
|
onPerfEntry({
|
|
name: 'FCP',
|
|
value: entry.startTime,
|
|
delta: entry.startTime,
|
|
id: `fcp-${Date.now()}`,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'paint', buffered: true });
|
|
};
|
|
|
|
const getLCP = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
const entries = list.getEntries();
|
|
const lastEntry = entries[entries.length - 1];
|
|
|
|
onPerfEntry({
|
|
name: 'LCP',
|
|
value: lastEntry.startTime,
|
|
delta: lastEntry.startTime,
|
|
id: `lcp-${Date.now()}`,
|
|
});
|
|
});
|
|
|
|
observer.observe({ type: 'largest-contentful-paint', buffered: true });
|
|
};
|
|
|
|
const getTTFB = (onPerfEntry: (metric: Metric) => void) => {
|
|
const observer = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (entry.entryType === 'navigation') {
|
|
const navEntry = entry as PerformanceNavigationTiming;
|
|
onPerfEntry({
|
|
name: 'TTFB',
|
|
value: navEntry.responseStart - navEntry.fetchStart,
|
|
delta: navEntry.responseStart - navEntry.fetchStart,
|
|
id: `ttfb-${Date.now()}`,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe({ type: 'navigation', buffered: true });
|
|
};
|
|
|
|
// Custom hook for Web Vitals tracking
|
|
export const useWebVitals = () => {
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
// Track Core Web Vitals
|
|
getCLS((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getFID((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getFCP((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getLCP((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
getTTFB((metric) => {
|
|
trackWebVitals({
|
|
...metric,
|
|
name: metric.name as 'CLS' | 'FID' | 'FCP' | 'LCP' | 'TTFB',
|
|
url: window.location.pathname,
|
|
});
|
|
});
|
|
|
|
// Track page load performance
|
|
const handleLoad = () => {
|
|
setTimeout(() => {
|
|
trackPerformance({
|
|
name: 'page-load-complete',
|
|
value: performance.now(),
|
|
url: window.location.pathname,
|
|
timestamp: Date.now(),
|
|
userAgent: navigator.userAgent,
|
|
});
|
|
}, 0);
|
|
};
|
|
|
|
if (document.readyState === 'complete') {
|
|
handleLoad();
|
|
} else {
|
|
window.addEventListener('load', handleLoad);
|
|
}
|
|
|
|
return () => {
|
|
window.removeEventListener('load', handleLoad);
|
|
};
|
|
}, []);
|
|
};
|