Some checks failed
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
160 lines
4.3 KiB
TypeScript
160 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import type { AnalyticsEvent, AnalyticsProvider, UserData } from "../core/types";
|
|
|
|
declare global {
|
|
interface Window {
|
|
rybbit?: {
|
|
event: (eventName: string, eventData?: Record<string, any>) => void;
|
|
pageview: () => void;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class RybbitProvider implements AnalyticsProvider {
|
|
name = "Rybbit";
|
|
private isClient: boolean;
|
|
|
|
constructor() {
|
|
this.isClient = typeof window !== "undefined";
|
|
}
|
|
|
|
isAvailable(): boolean {
|
|
return this.isClient && typeof window.rybbit?.event === "function";
|
|
}
|
|
|
|
private trackEvent(eventName: string, properties?: Record<string, unknown>): void {
|
|
if (!this.isAvailable()) {
|
|
console.warn(`[Rybbit] Not available for event: ${eventName}`);
|
|
return;
|
|
}
|
|
try {
|
|
window.rybbit!.event(eventName, properties);
|
|
} catch (e) {
|
|
console.warn(`[Rybbit] Tracking error for ${eventName}:`, e);
|
|
}
|
|
}
|
|
|
|
track(event: AnalyticsEvent): void {
|
|
switch (event.type) {
|
|
case "product_viewed":
|
|
this.trackEvent("product_view", {
|
|
product_id: event.product.id,
|
|
product_name: event.product.name,
|
|
price: event.product.price,
|
|
currency: event.product.currency,
|
|
category: event.product.category,
|
|
variant: event.product.variant,
|
|
});
|
|
break;
|
|
|
|
case "add_to_cart":
|
|
this.trackEvent("add_to_cart", {
|
|
product_id: event.product.id,
|
|
product_name: event.product.name,
|
|
price: event.product.price,
|
|
currency: event.product.currency,
|
|
quantity: event.product.quantity,
|
|
variant: event.product.variant,
|
|
});
|
|
break;
|
|
|
|
case "remove_from_cart":
|
|
this.trackEvent("remove_from_cart", {
|
|
product_id: event.product.id,
|
|
product_name: event.product.name,
|
|
quantity: event.product.quantity,
|
|
});
|
|
break;
|
|
|
|
case "cart_view":
|
|
this.trackEvent("cart_view", {
|
|
cart_total: event.cart.total,
|
|
currency: event.cart.currency,
|
|
item_count: event.cart.item_count,
|
|
});
|
|
break;
|
|
|
|
case "checkout_started":
|
|
this.trackEvent("checkout_started", {
|
|
cart_total: event.cart.total,
|
|
currency: event.cart.currency,
|
|
item_count: event.cart.item_count,
|
|
items: event.cart.items,
|
|
});
|
|
break;
|
|
|
|
case "checkout_step":
|
|
this.trackEvent("checkout_step", {
|
|
step: event.step,
|
|
...event.data,
|
|
});
|
|
break;
|
|
|
|
case "order_completed":
|
|
this.trackEvent("order_completed", {
|
|
order_id: event.order.order_id,
|
|
order_number: event.order.order_number,
|
|
total: event.order.total,
|
|
currency: event.order.currency,
|
|
item_count: event.order.item_count,
|
|
shipping_cost: event.order.shipping_cost,
|
|
coupon_code: event.order.coupon_code,
|
|
customer_email: event.order.customer_email,
|
|
payment_method: event.order.payment_method,
|
|
revenue: event.order.total,
|
|
});
|
|
break;
|
|
|
|
case "search":
|
|
this.trackEvent("search", {
|
|
query: event.query,
|
|
results_count: event.results_count,
|
|
});
|
|
break;
|
|
|
|
case "external_link_click":
|
|
this.trackEvent("external_link_click", {
|
|
url: event.url,
|
|
label: event.label,
|
|
});
|
|
break;
|
|
|
|
case "wishlist_add":
|
|
this.trackEvent("wishlist_add", {
|
|
product_id: event.product.id,
|
|
product_name: event.product.name,
|
|
});
|
|
break;
|
|
|
|
case "user_login":
|
|
this.trackEvent("user_login", {
|
|
method: event.method,
|
|
});
|
|
break;
|
|
|
|
case "user_register":
|
|
this.trackEvent("user_register", {
|
|
method: event.method,
|
|
});
|
|
break;
|
|
|
|
case "newsletter_signup":
|
|
this.trackEvent("newsletter_signup", {
|
|
email: event.email,
|
|
source: event.source,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
identify(_user: UserData): void {
|
|
// Rybbit doesn't have explicit identify - it's handled automatically via cookies
|
|
}
|
|
|
|
revenue?(_amount: number, _currency: string, _properties?: Record<string, unknown>): Promise<void> {
|
|
// Revenue is tracked via order_completed event
|
|
return Promise.resolve();
|
|
}
|
|
}
|