- Create new API route /api/rybbit/track to proxy Rybbit tracking requests - Extract real client IP from Cloudflare headers (cf-connecting-ip) - Forward X-Forwarded-For and X-Real-IP headers to analytics backends - Update OpenPanel proxy to also forward client IP - Update next.config.ts rewrite to use internal API route This fixes geo-location issues where all traffic appeared to come from Cloudflare edge locations instead of actual visitor countries.
211 lines
5.2 KiB
TypeScript
211 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
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 trackProductView = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
price: number;
|
|
currency: string;
|
|
category?: string;
|
|
}) => {
|
|
trackRybbitProductView({
|
|
id: product.id,
|
|
name: product.name,
|
|
price: product.price,
|
|
currency: product.currency,
|
|
category: product.category,
|
|
});
|
|
}, []);
|
|
|
|
const trackAddToCart = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
price: number;
|
|
currency: string;
|
|
quantity: number;
|
|
variant?: string;
|
|
}) => {
|
|
trackRybbitAddToCart({
|
|
id: product.id,
|
|
name: product.name,
|
|
price: product.price,
|
|
currency: product.currency,
|
|
quantity: product.quantity,
|
|
variant: product.variant,
|
|
});
|
|
}, []);
|
|
|
|
const trackRemoveFromCart = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
quantity: number;
|
|
}) => {
|
|
trackRybbitRemoveFromCart({
|
|
id: product.id,
|
|
name: product.name,
|
|
quantity: product.quantity,
|
|
});
|
|
}, []);
|
|
|
|
const trackCartView = useCallback((cart: {
|
|
total: number;
|
|
currency: string;
|
|
item_count: number;
|
|
}) => {
|
|
trackRybbitCartView({
|
|
total: cart.total,
|
|
currency: cart.currency,
|
|
item_count: cart.item_count,
|
|
});
|
|
}, []);
|
|
|
|
const trackCheckoutStarted = useCallback((cart: {
|
|
total: number;
|
|
currency: string;
|
|
item_count: number;
|
|
items: Array<{
|
|
id: string;
|
|
name: string;
|
|
quantity: number;
|
|
price: number;
|
|
}>;
|
|
}) => {
|
|
trackRybbitCheckoutStarted({
|
|
total: cart.total,
|
|
currency: cart.currency,
|
|
item_count: cart.item_count,
|
|
items: cart.items,
|
|
});
|
|
}, []);
|
|
|
|
const trackCheckoutStep = useCallback((step: string, data?: Record<string, unknown>) => {
|
|
trackRybbitCheckoutStep(step, data);
|
|
}, []);
|
|
|
|
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);
|
|
|
|
// Rybbit tracking
|
|
trackRybbitOrderCompleted({
|
|
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,
|
|
});
|
|
|
|
// 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);
|
|
}
|
|
}, []);
|
|
|
|
const trackSearch = useCallback((query: string, results_count: number) => {
|
|
trackRybbitSearch(query, results_count);
|
|
}, []);
|
|
|
|
const trackExternalLink = useCallback((url: string, label?: string) => {
|
|
trackRybbitExternalLink(url, label);
|
|
}, []);
|
|
|
|
const trackWishlistAdd = useCallback((product: {
|
|
id: string;
|
|
name: string;
|
|
}) => {
|
|
trackRybbitWishlistAdd({
|
|
id: product.id,
|
|
name: product.name,
|
|
});
|
|
}, []);
|
|
|
|
const trackUserLogin = useCallback((method: string) => {
|
|
trackRybbitUserLogin(method);
|
|
}, []);
|
|
|
|
const trackUserRegister = useCallback((method: string) => {
|
|
trackRybbitUserRegister(method);
|
|
}, []);
|
|
|
|
const trackNewsletterSignup = useCallback((email: string, source: string) => {
|
|
trackRybbitNewsletterSignup(email, source);
|
|
}, []);
|
|
|
|
// No-op placeholder for identifyUser (OpenPanel removed)
|
|
const identifyUser = useCallback((_user: {
|
|
profileId: string;
|
|
email?: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
}) => {
|
|
// OpenPanel was removed - this is now a no-op
|
|
// User identification is handled by Rybbit automatically via cookies
|
|
}, []);
|
|
|
|
return {
|
|
trackProductView,
|
|
trackAddToCart,
|
|
trackRemoveFromCart,
|
|
trackCartView,
|
|
trackCheckoutStarted,
|
|
trackCheckoutStep,
|
|
trackOrderCompleted,
|
|
trackSearch,
|
|
trackExternalLink,
|
|
trackWishlistAdd,
|
|
trackUserLogin,
|
|
trackUserRegister,
|
|
trackNewsletterSignup,
|
|
identifyUser,
|
|
};
|
|
}
|
|
|
|
export default useAnalytics;
|