Some checks failed
Build and Deploy / build (push) Has been cancelled
- Add Rybbit server-side tracking to analytics-server.ts for order completion and revenue - Add trackNewsletterSignup to analytics.ts and wire up NewsletterSection - Add cart tracking to CartDrawer (cart view, remove from cart) - All ecommerce events now track to both OpenPanel and Rybbit
358 lines
9.4 KiB
TypeScript
358 lines
9.4 KiB
TypeScript
"use client";
|
|
|
|
import { useOpenPanel } from "@openpanel/nextjs";
|
|
import { useCallback } from "react";
|
|
import {
|
|
trackRybbitProductView,
|
|
trackRybbitAddToCart,
|
|
trackRybbitRemoveFromCart,
|
|
trackRybbitCheckoutStarted,
|
|
trackRybbitCheckoutStep,
|
|
trackRybbitOrderCompleted,
|
|
trackRybbitSearch,
|
|
trackRybbitExternalLink,
|
|
trackRybbitCartView,
|
|
trackRybbitWishlistAdd,
|
|
trackRybbitUserLogin,
|
|
trackRybbitUserRegister,
|
|
trackRybbitNewsletterSignup,
|
|
} from "@/lib/services/RybbitService";
|
|
|
|
export function useAnalytics() {
|
|
const op = useOpenPanel();
|
|
|
|
// Helper to track with both OpenPanel and Rybbit
|
|
const trackDual = useCallback((
|
|
eventName: string,
|
|
openPanelData: Record<string, any>
|
|
) => {
|
|
// OpenPanel tracking
|
|
try {
|
|
op.track(eventName, openPanelData);
|
|
} catch (e) {
|
|
console.error("[OpenPanel] Tracking error:", e);
|
|
}
|
|
|
|
// Rybbit tracking (fire-and-forget)
|
|
try {
|
|
switch (eventName) {
|
|
case "product_viewed":
|
|
trackRybbitProductView({
|
|
id: openPanelData.product_id,
|
|
name: openPanelData.product_name,
|
|
price: openPanelData.price,
|
|
currency: openPanelData.currency,
|
|
category: openPanelData.category,
|
|
});
|
|
break;
|
|
case "add_to_cart":
|
|
trackRybbitAddToCart({
|
|
id: openPanelData.product_id,
|
|
name: openPanelData.product_name,
|
|
price: openPanelData.price,
|
|
currency: openPanelData.currency,
|
|
quantity: openPanelData.quantity,
|
|
variant: openPanelData.variant,
|
|
});
|
|
break;
|
|
case "remove_from_cart":
|
|
trackRybbitRemoveFromCart({
|
|
id: openPanelData.product_id,
|
|
name: openPanelData.product_name,
|
|
quantity: openPanelData.quantity,
|
|
});
|
|
break;
|
|
case "cart_view":
|
|
trackRybbitCartView({
|
|
total: openPanelData.cart_total,
|
|
currency: openPanelData.currency,
|
|
item_count: openPanelData.item_count,
|
|
});
|
|
break;
|
|
case "checkout_started":
|
|
trackRybbitCheckoutStarted({
|
|
total: openPanelData.cart_total,
|
|
currency: openPanelData.currency,
|
|
item_count: openPanelData.item_count,
|
|
items: openPanelData.items,
|
|
});
|
|
break;
|
|
case "checkout_step":
|
|
trackRybbitCheckoutStep(openPanelData.step, openPanelData);
|
|
break;
|
|
case "order_completed":
|
|
trackRybbitOrderCompleted({
|
|
order_id: openPanelData.order_id,
|
|
order_number: openPanelData.order_number,
|
|
total: openPanelData.total,
|
|
currency: openPanelData.currency,
|
|
item_count: openPanelData.item_count,
|
|
shipping_cost: openPanelData.shipping_cost,
|
|
customer_email: openPanelData.customer_email,
|
|
payment_method: openPanelData.payment_method,
|
|
});
|
|
break;
|
|
case "search":
|
|
trackRybbitSearch(openPanelData.query, openPanelData.results_count);
|
|
break;
|
|
case "external_link_click":
|
|
trackRybbitExternalLink(openPanelData.url, openPanelData.label);
|
|
break;
|
|
case "wishlist_add":
|
|
trackRybbitWishlistAdd({
|
|
id: openPanelData.product_id,
|
|
name: openPanelData.product_name,
|
|
});
|
|
break;
|
|
case "user_login":
|
|
trackRybbitUserLogin(openPanelData.method);
|
|
break;
|
|
case "user_register":
|
|
trackRybbitUserRegister(openPanelData.method);
|
|
break;
|
|
case "newsletter_signup":
|
|
trackRybbitNewsletterSignup(openPanelData.email, openPanelData.source);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.warn("[Rybbit] Tracking error:", e);
|
|
}
|
|
}, [op]);
|
|
|
|
const trackProductView = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
price: number;
|
|
currency: string;
|
|
category?: string;
|
|
}) => {
|
|
trackDual("product_viewed", {
|
|
product_id: product.id,
|
|
product_name: product.name,
|
|
price: product.price,
|
|
currency: product.currency,
|
|
category: product.category,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackAddToCart = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
price: number;
|
|
currency: string;
|
|
quantity: number;
|
|
variant?: string;
|
|
}) => {
|
|
trackDual("add_to_cart", {
|
|
product_id: product.id,
|
|
product_name: product.name,
|
|
price: product.price,
|
|
currency: product.currency,
|
|
quantity: product.quantity,
|
|
variant: product.variant,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackRemoveFromCart = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
quantity: number;
|
|
}) => {
|
|
trackDual("remove_from_cart", {
|
|
product_id: product.id,
|
|
product_name: product.name,
|
|
quantity: product.quantity,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackCartView = useCallback((cart: {
|
|
total: number;
|
|
currency: string;
|
|
item_count: number;
|
|
}) => {
|
|
trackDual("cart_view", {
|
|
cart_total: cart.total,
|
|
currency: cart.currency,
|
|
item_count: cart.item_count,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackCheckoutStarted = useCallback((cart: {
|
|
total: number;
|
|
currency: string;
|
|
item_count: number;
|
|
items: Array<{
|
|
id: string;
|
|
name: string;
|
|
quantity: number;
|
|
price: number;
|
|
}>;
|
|
}) => {
|
|
trackDual("checkout_started", {
|
|
cart_total: cart.total,
|
|
currency: cart.currency,
|
|
item_count: cart.item_count,
|
|
items: cart.items,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackCheckoutStep = useCallback((step: string, data?: Record<string, unknown>) => {
|
|
trackDual("checkout_step", {
|
|
step,
|
|
...data,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackOrderCompleted = useCallback(async (order: {
|
|
order_id: string;
|
|
order_number: string;
|
|
total: number;
|
|
currency: string;
|
|
item_count: number;
|
|
shipping_cost?: number;
|
|
customer_email?: string;
|
|
payment_method?: string;
|
|
}) => {
|
|
console.log("[Analytics] Tracking order:", order.order_number);
|
|
|
|
// Track with both OpenPanel and Rybbit
|
|
trackDual("order_completed", {
|
|
order_id: order.order_id,
|
|
order_number: order.order_number,
|
|
total: order.total,
|
|
currency: order.currency,
|
|
item_count: order.item_count,
|
|
shipping_cost: order.shipping_cost,
|
|
customer_email: order.customer_email,
|
|
payment_method: order.payment_method,
|
|
source: "client",
|
|
});
|
|
|
|
// OpenPanel revenue tracking
|
|
try {
|
|
op.revenue(order.total, {
|
|
currency: order.currency,
|
|
transaction_id: order.order_number,
|
|
source: "client",
|
|
});
|
|
} catch (e) {
|
|
console.error("[OpenPanel] Revenue tracking error:", e);
|
|
}
|
|
|
|
// Server-side tracking for reliability
|
|
try {
|
|
const response = await fetch("/api/analytics/track-order", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
orderId: order.order_id,
|
|
orderNumber: order.order_number,
|
|
total: order.total,
|
|
currency: order.currency,
|
|
itemCount: order.item_count,
|
|
customerEmail: order.customer_email,
|
|
paymentMethod: order.payment_method,
|
|
shippingCost: order.shipping_cost,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
console.error("[Server Analytics] Failed:", await response.text());
|
|
}
|
|
} catch (e) {
|
|
console.error("[Server Analytics] API call failed:", e);
|
|
}
|
|
}, [op, trackDual]);
|
|
|
|
const trackSearch = useCallback((query: string, results_count: number) => {
|
|
trackDual("search", {
|
|
query,
|
|
results_count,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackExternalLink = useCallback((url: string, label?: string) => {
|
|
trackDual("external_link_click", {
|
|
url,
|
|
label,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackWishlistAdd = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
}) => {
|
|
trackDual("wishlist_add", {
|
|
product_id: product.id,
|
|
product_name: product.name,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackUserLogin = useCallback((method: string) => {
|
|
trackDual("user_login", {
|
|
method,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackUserRegister = useCallback((method: string) => {
|
|
trackDual("user_register", {
|
|
method,
|
|
source: "client",
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const trackNewsletterSignup = useCallback((email: string, source: string) => {
|
|
trackDual("newsletter_signup", {
|
|
email,
|
|
source,
|
|
});
|
|
}, [trackDual]);
|
|
|
|
const identifyUser = useCallback((user: {
|
|
profileId: string;
|
|
email?: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
}) => {
|
|
try {
|
|
op.identify({
|
|
profileId: user.profileId,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
email: user.email,
|
|
});
|
|
} catch (e) {
|
|
console.error("[OpenPanel] Identify error:", e);
|
|
}
|
|
}, [op]);
|
|
|
|
return {
|
|
trackProductView,
|
|
trackAddToCart,
|
|
trackRemoveFromCart,
|
|
trackCartView,
|
|
trackCheckoutStarted,
|
|
trackCheckoutStep,
|
|
trackOrderCompleted,
|
|
trackSearch,
|
|
trackExternalLink,
|
|
trackWishlistAdd,
|
|
trackUserLogin,
|
|
trackUserRegister,
|
|
trackNewsletterSignup,
|
|
identifyUser,
|
|
};
|
|
}
|
|
|
|
export default useAnalytics;
|