Files
mintel.me/apps/web/src/utils/analytics/index.ts
Marc Mintel 2038b8fe47
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
Build & Deploy / 🧪 QA (push) Failing after 1m18s
Build & Deploy / 🏗️ Build (push) Failing after 2m58s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
feat(analytics): implement advanced tracking with script-less smart proxy
- Added Umami Smart Proxy route handler
- Refactored Umami adapter to use proxy-based fetch
- Implemented TrackedButton, TrackedLink, and ScrollDepthTracker
- Integrated event tracking into ContactForm
- Enhanced Analytics component with manual pageview and performance tracking
2026-02-16 23:03:42 +01:00

138 lines
3.5 KiB
TypeScript

/**
* Analytics Service - Main entry point with DI
* Clean constructor-based dependency injection
*/
import type {
AnalyticsAdapter,
AnalyticsEvent,
AnalyticsConfig,
} from "./interfaces";
import { PlausibleAdapter } from "./plausible-adapter";
import { UmamiAdapter, type UmamiConfig } from "./umami-adapter";
export class AnalyticsService {
private adapter: AnalyticsAdapter;
/**
* Create analytics service with dependency injection
* @param adapter - Analytics adapter implementation
*/
constructor(adapter: AnalyticsAdapter) {
this.adapter = adapter;
}
getAdapter(): AnalyticsAdapter {
return this.adapter;
}
async track(event: AnalyticsEvent): Promise<void> {
return this.adapter.track(event);
}
async page(path: string, props?: Record<string, any>): Promise<void> {
if (this.adapter.page) {
return this.adapter.page(path, props);
}
return this.track({ name: "Pageview", props: { path, ...props } });
}
async identify(userId: string, traits?: Record<string, any>): Promise<void> {
if (this.adapter.identify) {
return this.adapter.identify(userId, traits);
}
}
// Convenience methods
async trackEvent(name: string, props?: Record<string, any>): Promise<void> {
return this.track({ name, props });
}
async trackOutboundLink(url: string, text: string): Promise<void> {
return this.track({
name: "Outbound Link",
props: { url, text },
});
}
async trackSearch(query: string, path: string): Promise<void> {
return this.track({
name: "Search",
props: { query, path },
});
}
async trackPageLoad(
loadTime: number,
path: string,
userAgent: string,
): Promise<void> {
return this.track({
name: "Page Load",
props: { loadTime: Math.round(loadTime), path, userAgent },
});
}
}
// Factory functions
export function createPlausibleAnalytics(
config: AnalyticsConfig,
): AnalyticsService {
return new AnalyticsService(new PlausibleAdapter(config));
}
export function createUmamiAnalytics(config: UmamiConfig): AnalyticsService {
return new AnalyticsService(new UmamiAdapter(config));
}
// Default singleton
let defaultAnalytics: AnalyticsService | null = null;
import { env } from "@/lib/env";
export function getDefaultAnalytics(): AnalyticsService {
if (!defaultAnalytics) {
const provider = env.NEXT_PUBLIC_ANALYTICS_PROVIDER;
if (provider === "umami") {
defaultAnalytics = createUmamiAnalytics({
hostUrl: env.UMAMI_API_ENDPOINT,
});
} else if (provider === "plausible") {
defaultAnalytics = createPlausibleAnalytics({
domain: env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN,
scriptUrl: env.NEXT_PUBLIC_PLAUSIBLE_SCRIPT_URL,
});
} else {
// No analytics provider configured
return {
getAdapter: () => ({
track: async () => {},
page: async () => {},
getScriptTag: () => null,
}),
track: async () => {},
page: async () => {},
identify: async () => {},
trackEvent: async () => {},
trackOutboundLink: async () => {},
trackSearch: async () => {},
trackPageLoad: async () => {},
} as any;
}
}
return defaultAnalytics;
}
// Convenience function
export async function track(
name: string,
props?: Record<string, any>,
): Promise<void> {
return getDefaultAnalytics().trackEvent(name, props);
}
// Re-export for advanced usage
export type { AnalyticsAdapter, AnalyticsEvent, AnalyticsConfig };
export { PlausibleAdapter };