66829aeffd
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
113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
"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;
|
|
}
|