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
- 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
138 lines
3.5 KiB
TypeScript
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 };
|