8840c4398a
- Downgrade Next.js 16→14.2, React 19→18.3, Tailwind 4→3.4 - Add comprehensive GA4 error monitoring system - Create Jenkins CI/CD pipeline with quality gates - Fix build issues: ESLint, SWC conflict, config format - Add documentation for deployment and error tracking
194 lines
4.9 KiB
TypeScript
194 lines
4.9 KiB
TypeScript
declare global {
|
|
interface Window {
|
|
gtag: (...args: unknown[]) => void;
|
|
dataLayer: unknown[];
|
|
}
|
|
}
|
|
|
|
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || '';
|
|
|
|
export interface CookiePreferences {
|
|
necessary: boolean;
|
|
analytics: boolean;
|
|
marketing: boolean;
|
|
functionality: boolean;
|
|
timestamp?: number;
|
|
}
|
|
|
|
const DEFAULT_PREFERENCES: CookiePreferences = {
|
|
necessary: true,
|
|
analytics: true,
|
|
marketing: false,
|
|
functionality: true,
|
|
};
|
|
|
|
export function getDefaultPreferences(): CookiePreferences {
|
|
return { ...DEFAULT_PREFERENCES };
|
|
}
|
|
|
|
export function getStoredPreferences(): CookiePreferences | null {
|
|
if (typeof window === 'undefined') return null;
|
|
|
|
try {
|
|
const stored = localStorage.getItem('novalon-cookie-preferences');
|
|
if (stored) {
|
|
return JSON.parse(stored) as CookiePreferences;
|
|
}
|
|
} catch (e) {
|
|
console.warn('[Analytics] Failed to read cookie preferences:', e);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function storePreferences(preferences: CookiePreferences): void {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
try {
|
|
localStorage.setItem(
|
|
'novalon-cookie-preferences',
|
|
JSON.stringify(preferences)
|
|
);
|
|
} catch (e) {
|
|
console.warn('[Analytics] Failed to store cookie preferences:', e);
|
|
}
|
|
}
|
|
|
|
export function updateConsentDetailed(preferences: CookiePreferences): void {
|
|
if (typeof window === 'undefined' || !window.gtag) return;
|
|
|
|
window.gtag('consent', 'update', {
|
|
analytics_storage: preferences.analytics ? 'granted' : 'denied',
|
|
ad_storage: preferences.marketing ? 'granted' : 'denied',
|
|
functionality_storage: preferences.functionality ? 'granted' : 'denied',
|
|
});
|
|
}
|
|
|
|
export function trackEvent(
|
|
action: string,
|
|
category: string,
|
|
label?: string,
|
|
value?: number
|
|
): void {
|
|
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
|
|
return;
|
|
}
|
|
|
|
window.gtag('event', action, {
|
|
event_category: category,
|
|
event_label: label,
|
|
event_value: value,
|
|
send_to: GA_MEASUREMENT_ID,
|
|
});
|
|
}
|
|
|
|
export function trackButtonClick(
|
|
buttonName: string,
|
|
_pageLocation?: string
|
|
): void {
|
|
trackEvent('button_click', 'engagement', buttonName, undefined);
|
|
}
|
|
|
|
export function trackError(
|
|
errorType: string,
|
|
message: string,
|
|
fatal: boolean = false,
|
|
additionalContext?: Record<string, string | number | boolean>
|
|
): void {
|
|
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
|
|
console.warn('[GA4] Error tracking not available:', { errorType, message });
|
|
return;
|
|
}
|
|
|
|
const errorData: Record<string, string | number | boolean> = {
|
|
description: `[${errorType}] ${message}`,
|
|
fatal: fatal ? 'true' : 'false',
|
|
url: typeof window !== 'undefined' ? window.location.href : '',
|
|
timestamp: new Date().toISOString(),
|
|
...additionalContext,
|
|
};
|
|
|
|
window.gtag('event', 'exception', {
|
|
...errorData,
|
|
send_to: GA_MEASUREMENT_ID,
|
|
});
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.log('[GA4] Error tracked:', errorData);
|
|
}
|
|
}
|
|
|
|
export function trackPageView(url: string, title: string): void {
|
|
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
|
|
return;
|
|
}
|
|
|
|
window.gtag('config', GA_MEASUREMENT_ID, {
|
|
page_path: url,
|
|
page_title: title,
|
|
});
|
|
}
|
|
|
|
export function trackPerformance(
|
|
metricName: string,
|
|
value: number,
|
|
category: string = 'web_vitals'
|
|
): void {
|
|
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
|
|
return;
|
|
}
|
|
|
|
window.gtag('event', metricName, {
|
|
event_category: category,
|
|
event_value: Math.round(value),
|
|
value: Math.round(value),
|
|
send_to: GA_MEASUREMENT_ID,
|
|
});
|
|
}
|
|
|
|
export function trackContactForm(
|
|
formData: { name: string; email: string; company: string } | string,
|
|
success?: boolean
|
|
): void {
|
|
if (typeof formData === 'string') {
|
|
trackEvent('form_submit', 'contact', formData, success ? 1 : 0);
|
|
} else {
|
|
trackEvent('form_submit', 'contact', formData.company, success !== false ? 1 : 0);
|
|
}
|
|
|
|
if (success !== false) {
|
|
trackConversion('contact_form_submit', 1);
|
|
}
|
|
}
|
|
|
|
export function trackConversion(conversionLabel: string, value?: number): void {
|
|
if (typeof window === 'undefined' || !window.gtag || !GA_MEASUREMENT_ID) {
|
|
return;
|
|
}
|
|
|
|
window.gtag('event', 'conversion', {
|
|
send_to: GA_MEASUREMENT_ID,
|
|
transaction_id: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
value: value || 1,
|
|
currency: 'CNY',
|
|
conversion_label: conversionLabel,
|
|
});
|
|
}
|
|
|
|
export function trackOutboundLink(url: string, _linkText?: string): void {
|
|
trackEvent('outbound_click', 'engagement', url);
|
|
|
|
if (typeof window !== 'undefined' && window.gtag && GA_MEASUREMENT_ID) {
|
|
window.gtag('event', 'click', {
|
|
event_category: 'outbound',
|
|
event_label: url,
|
|
transport_type: 'beacon',
|
|
send_to: GA_MEASUREMENT_ID,
|
|
});
|
|
}
|
|
}
|
|
|
|
export function trackScrollDepth(percentage: number, _maxScroll?: number): void {
|
|
trackEvent(`scroll_${percentage}`, 'engagement', `${percentage}%`);
|
|
}
|