refactor(analytics): abstract analytics into provider pattern
Build and Deploy / build (push) Has been cancelled
Build and Deploy / build (push) Has been cancelled
- Add type-safe AnalyticsEvent union types - Create AnalyticsProvider interface for pluggable analytics backends - Implement OpenPanelProvider and RybbitProvider adapters - Create AnalyticsTracker that fans out events to all providers - Simplifies adding new analytics platforms in the future
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"use client";
|
||||
|
||||
import type { AnalyticsEvent, AnalyticsProvider, UserData } from "./types";
|
||||
|
||||
export class AnalyticsTracker {
|
||||
private providers: AnalyticsProvider[] = [];
|
||||
|
||||
addProvider(provider: AnalyticsProvider): void {
|
||||
this.providers.push(provider);
|
||||
}
|
||||
|
||||
track(event: AnalyticsEvent): void {
|
||||
for (const provider of this.providers) {
|
||||
try {
|
||||
provider.track(event);
|
||||
} catch (e) {
|
||||
console.error(`[Analytics] ${provider.name} tracking error:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
identify(user: UserData): void {
|
||||
for (const provider of this.providers) {
|
||||
if (provider.identify) {
|
||||
try {
|
||||
provider.identify(user);
|
||||
} catch (e) {
|
||||
console.error(`[Analytics] ${provider.name} identify error:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async revenue(amount: number, currency: string, properties?: Record<string, unknown>): Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const provider of this.providers) {
|
||||
if (provider.revenue) {
|
||||
promises.push(
|
||||
provider.revenue(amount, currency, properties).catch((e) => {
|
||||
console.error(`[Analytics] ${provider.name} revenue error:`, e);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
productViewed(product: { id: string; name: string; price: number; currency: string; category?: string; variant?: string }) {
|
||||
this.track({ type: "product_viewed", product });
|
||||
}
|
||||
|
||||
addToCart(product: { id: string; name: string; price: number; currency: string; quantity: number; variant?: string }) {
|
||||
this.track({ type: "add_to_cart", product });
|
||||
}
|
||||
|
||||
removeFromCart(product: { id: string; name: string; quantity: number }) {
|
||||
this.track({ type: "remove_from_cart", product });
|
||||
}
|
||||
|
||||
cartViewed(cart: { total: number; currency: string; item_count: number }) {
|
||||
this.track({ type: "cart_view", cart });
|
||||
}
|
||||
|
||||
checkoutStarted(cart: { total: number; currency: string; item_count: number; items?: Array<{ id: string; name: string; quantity: number; price: number }> }) {
|
||||
this.track({ type: "checkout_started", cart });
|
||||
}
|
||||
|
||||
checkoutStep(step: string, data?: Record<string, unknown>) {
|
||||
this.track({ type: "checkout_step", step, data });
|
||||
}
|
||||
|
||||
orderCompleted(order: { order_id: string; order_number: string; total: number; currency: string; item_count: number; shipping_cost?: number; coupon_code?: string; customer_email?: string; payment_method?: string }) {
|
||||
this.track({ type: "order_completed", order });
|
||||
this.revenue(order.total, order.currency, {
|
||||
transaction_id: order.order_number,
|
||||
order_id: order.order_id,
|
||||
});
|
||||
}
|
||||
|
||||
searchPerformed(query: string, results_count: number) {
|
||||
this.track({ type: "search", query, results_count });
|
||||
}
|
||||
|
||||
externalLinkClicked(url: string, label?: string) {
|
||||
this.track({ type: "external_link_click", url, label });
|
||||
}
|
||||
|
||||
wishlistAdded(product: { id: string; name: string }) {
|
||||
this.track({ type: "wishlist_add", product });
|
||||
}
|
||||
|
||||
userLoggedIn(method: string) {
|
||||
this.track({ type: "user_login", method });
|
||||
}
|
||||
|
||||
userRegistered(method: string) {
|
||||
this.track({ type: "user_register", method });
|
||||
}
|
||||
|
||||
newsletterSignedUp(email: string, source: string) {
|
||||
this.track({ type: "newsletter_signup", email, source });
|
||||
}
|
||||
}
|
||||
|
||||
let trackerInstance: AnalyticsTracker | null = null;
|
||||
|
||||
export function getTracker(): AnalyticsTracker {
|
||||
if (!trackerInstance) {
|
||||
trackerInstance = new AnalyticsTracker();
|
||||
}
|
||||
return trackerInstance;
|
||||
}
|
||||
Reference in New Issue
Block a user