fix: Simplify analytics to fix OpenPanel errors

Remove complex tracking implementation that was causing errors:
- Remove op.increment/decrement calls (causing Duplicate event errors)
- Remove complex type definitions
- Remove unused tracking methods
- Keep only essential tracking with proper error handling

This reverts to a simpler, working analytics implementation.
This commit is contained in:
Unchained
2026-03-29 20:41:27 +02:00
parent 51a41cbb89
commit aa737a1449

View File

@@ -1,480 +1,184 @@
"use client";
import { useOpenPanel } from "@openpanel/nextjs";
import { useCallback, useRef } from "react";
// E-commerce Events
export type ProductViewData = {
id: string;
name: string;
price: number;
currency: string;
category?: string;
sku?: string;
in_stock?: boolean;
};
export type CartItemData = {
id: string;
name: string;
price: number;
currency: string;
quantity: number;
variant?: string;
sku?: string;
};
export type CartData = {
total: number;
currency: string;
item_count: number;
items: CartItemData[];
coupon_code?: string;
};
export type CheckoutData = {
step: "email" | "shipping" | "billing" | "payment" | "review" | "complete";
value?: number;
currency?: string;
shipping_method?: string;
payment_method?: string;
error?: string;
};
export type OrderData = {
order_id: string;
order_number: string;
total: number;
currency: string;
item_count: number;
shipping_cost?: number;
customer_email?: string;
payment_method?: string;
coupon_code?: string;
};
// User Events
export type UserData = {
profileId: string;
email?: string;
firstName?: string;
lastName?: string;
phone?: string;
properties?: Record<string, unknown>;
};
export type SearchData = {
query: string;
results_count: number;
filters?: Record<string, string>;
category?: string;
};
export type EngagementData = {
element: string;
action: "click" | "hover" | "scroll" | "view";
value?: string | number;
metadata?: Record<string, unknown>;
};
import { useCallback } from "react";
export function useAnalytics() {
const op = useOpenPanel();
const startTimeRef = useRef<number>(Date.now());
// ==================== E-COMMERCE EVENTS ====================
/**
* Track when user views a product
*/
const trackProductView = useCallback((product: ProductViewData) => {
op.track("product_viewed", {
product_id: product.id,
product_name: product.name,
price: product.price,
currency: product.currency,
category: product.category,
sku: product.sku,
in_stock: product.in_stock,
});
// Also increment product view counter
op.increment({ product_views: 1 });
}, [op]);
/**
* Track when user views product image gallery
*/
const trackProductImageView = useCallback((productId: string, imageIndex: number) => {
op.track("product_image_viewed", {
product_id: productId,
image_index: imageIndex,
});
}, [op]);
/**
* Track variant selection
*/
const trackVariantSelect = useCallback((productId: string, variantName: string, price: number) => {
op.track("variant_selected", {
product_id: productId,
variant_name: variantName,
price: price,
});
}, [op]);
/**
* Track add to cart
*/
const trackAddToCart = useCallback((item: CartItemData) => {
op.track("add_to_cart", {
product_id: item.id,
product_name: item.name,
price: item.price,
currency: item.currency,
quantity: item.quantity,
variant: item.variant,
sku: item.sku,
value: item.price * item.quantity,
});
// Add to cart counter
op.increment({ items_added_to_cart: item.quantity });
}, [op]);
/**
* Track remove from cart
*/
const trackRemoveFromCart = useCallback((item: CartItemData) => {
op.track("remove_from_cart", {
product_id: item.id,
product_name: item.name,
price: item.price,
quantity: item.quantity,
variant: item.variant,
value: item.price * item.quantity,
});
}, [op]);
/**
* Track quantity change in cart
*/
const trackQuantityChange = useCallback((item: CartItemData, oldQuantity: number, newQuantity: number) => {
op.track("quantity_changed", {
product_id: item.id,
product_name: item.name,
old_quantity: oldQuantity,
new_quantity: newQuantity,
difference: newQuantity - oldQuantity,
});
}, [op]);
/**
* Track cart drawer open
*/
const trackCartOpen = useCallback((cart: CartData) => {
op.track("cart_opened", {
cart_total: cart.total,
currency: cart.currency,
item_count: cart.item_count,
items: cart.items.map(i => ({
id: i.id,
name: i.name,
quantity: i.quantity,
price: i.price,
})),
});
}, [op]);
/**
* Track cart abandonment
*/
const trackCartAbandonment = useCallback((cart: CartData, timeSpentMs: number) => {
op.track("cart_abandoned", {
cart_total: cart.total,
currency: cart.currency,
item_count: cart.item_count,
items: cart.items.map(i => i.id),
time_spent_seconds: Math.round(timeSpentMs / 1000),
value: cart.total,
});
// Track as pending revenue for recovery campaigns
op.pendingRevenue(cart.total, {
currency: cart.currency,
item_count: cart.item_count,
});
}, [op]);
/**
* Track checkout started
*/
const trackCheckoutStarted = useCallback((cart: CartData) => {
startTimeRef.current = Date.now();
op.track("checkout_started", {
cart_total: cart.total,
currency: cart.currency,
item_count: cart.item_count,
items: cart.items.map(i => ({
id: i.id,
name: i.name,
quantity: i.quantity,
price: i.price,
})),
coupon_code: cart.coupon_code,
});
}, [op]);
/**
* Track checkout step progression
*/
const trackCheckoutStep = useCallback((data: CheckoutData) => {
const eventName = `checkout_${data.step}`;
op.track(eventName, {
step: data.step,
value: data.value,
currency: data.currency,
shipping_method: data.shipping_method,
payment_method: data.payment_method,
error: data.error,
time_spent_ms: Date.now() - startTimeRef.current,
});
// If there's an error, track it separately
if (data.error) {
op.track("checkout_error", {
step: data.step,
error_message: data.error,
const trackProductView = useCallback((product: {
id: string;
name: string;
price: number;
currency: string;
category?: string;
}) => {
try {
op.track("product_viewed", {
product_id: product.id,
product_name: product.name,
price: product.price,
currency: product.currency,
category: product.category,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
/**
* Track payment method selection
*/
const trackPaymentMethodSelect = useCallback((method: string, subtotal: number) => {
op.track("payment_method_selected", {
method: method,
subtotal: subtotal,
});
}, [op]);
/**
* Track shipping method selection
*/
const trackShippingMethodSelect = useCallback((method: string, cost: number) => {
op.track("shipping_method_selected", {
method: method,
cost: cost,
});
}, [op]);
/**
* Track order completion with revenue
*/
const trackOrderCompleted = useCallback((order: OrderData) => {
const timeToComplete = Date.now() - startTimeRef.current;
// Track order event
op.track("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,
coupon_code: order.coupon_code,
time_to_complete_ms: timeToComplete,
});
// Track actual revenue
op.revenue(order.total, {
currency: order.currency,
transaction_id: order.order_number,
order_id: order.order_id,
item_count: order.item_count,
payment_method: order.payment_method,
shipping_cost: order.shipping_cost,
});
// Increment order counter for user
op.increment({
total_orders: 1,
total_revenue: order.total,
});
}, [op]);
// ==================== USER ENGAGEMENT EVENTS ====================
/**
* Track search queries
*/
const trackSearch = useCallback((data: SearchData) => {
op.track("search", {
query: data.query,
results_count: data.results_count,
filters: data.filters,
category: data.category,
});
}, [op]);
/**
* Track user engagement with elements
*/
const trackEngagement = useCallback((data: EngagementData) => {
op.track(`engagement_${data.action}`, {
element: data.element,
action: data.action,
value: data.value,
...data.metadata,
});
}, [op]);
/**
* Track CTA button clicks
*/
const trackCTAClick = useCallback((ctaName: string, location: string, destination?: string) => {
op.track("cta_click", {
cta_name: ctaName,
location: location,
destination: destination,
});
}, [op]);
/**
* Track external link clicks
*/
const trackExternalLink = useCallback((url: string, label?: string, location?: string) => {
op.track("external_link_click", {
url: url,
label: label,
location: location,
});
}, [op]);
/**
* Track newsletter signup
*/
const trackNewsletterSignup = useCallback((email: string, location: string) => {
op.track("newsletter_signup", {
email: email,
location: location,
});
op.increment({ newsletter_signups: 1 });
}, [op]);
/**
* Track promo code usage
*/
const trackPromoCode = useCallback((code: string, discount: number, success: boolean) => {
op.track("promo_code_applied", {
code: code,
discount: discount,
success: success,
});
}, [op]);
/**
* Track wishlist actions
*/
const trackWishlistAction = useCallback((action: "add" | "remove", productId: string, productName: string) => {
op.track(`wishlist_${action}`, {
product_id: productId,
product_name: productName,
});
if (action === "add") {
op.increment({ wishlist_items: 1 });
} else {
op.decrement({ wishlist_items: 1 });
const trackAddToCart = useCallback((product: {
id: string;
name: string;
price: number;
currency: string;
quantity: number;
variant?: string;
}) => {
try {
op.track("add_to_cart", {
product_id: product.id,
product_name: product.name,
price: product.price,
currency: product.currency,
quantity: product.quantity,
variant: product.variant,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
// ==================== USER IDENTIFICATION ====================
/**
* Identify user
*/
const identifyUser = useCallback((user: UserData) => {
op.identify({
profileId: user.profileId,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
properties: {
phone: user.phone,
...user.properties,
},
});
const trackRemoveFromCart = useCallback((product: {
id: string;
name: string;
quantity: number;
}) => {
try {
op.track("remove_from_cart", {
product_id: product.id,
product_name: product.name,
quantity: product.quantity,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
/**
* Set user properties
*/
const setUserProperties = useCallback((properties: Record<string, unknown>) => {
op.setGlobalProperties(properties);
const trackCheckoutStarted = useCallback((cart: {
total: number;
currency: string;
item_count: number;
items: Array<{
id: string;
name: string;
quantity: number;
price: number;
}>;
}) => {
try {
op.track("checkout_started", {
cart_total: cart.total,
currency: cart.currency,
item_count: cart.item_count,
items: cart.items,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
// ==================== SCREEN/SESSION TRACKING ====================
/**
* Track screen/page view
*/
const trackScreenView = useCallback((path: string, title?: string) => {
op.screenView(path, {
title: title,
url: window.location.href,
referrer: document.referrer,
});
const trackCheckoutStep = useCallback((step: string, data?: Record<string, unknown>) => {
try {
op.track("checkout_step", {
step,
...data,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
/**
* Track session start
*/
const trackSessionStart = useCallback(() => {
op.track("session_started", {
url: window.location.href,
referrer: document.referrer,
user_agent: navigator.userAgent,
screen_resolution: `${window.screen.width}x${window.screen.height}`,
});
const trackOrderCompleted = useCallback((order: {
order_id: string;
order_number: string;
total: number;
currency: string;
item_count: number;
shipping_cost?: number;
customer_email?: string;
}) => {
try {
// Track order event
op.track("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,
});
// Track revenue
op.revenue(order.total, {
currency: order.currency,
transaction_id: order.order_number,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
const trackSearch = useCallback((query: string, results_count: number) => {
try {
op.track("search", {
query,
results_count,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
const trackExternalLink = useCallback((url: string, label?: string) => {
try {
op.track("external_link_click", {
url,
label,
});
} catch (e) {
console.error("Track error:", e);
}
}, [op]);
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("Identify error:", e);
}
}, [op]);
return {
// E-commerce
trackProductView,
trackProductImageView,
trackVariantSelect,
trackAddToCart,
trackRemoveFromCart,
trackQuantityChange,
trackCartOpen,
trackCartAbandonment,
trackCheckoutStarted,
trackCheckoutStep,
trackPaymentMethodSelect,
trackShippingMethodSelect,
trackOrderCompleted,
// User Engagement
trackSearch,
trackEngagement,
trackCTAClick,
trackExternalLink,
trackNewsletterSignup,
trackPromoCode,
trackWishlistAction,
// User Identity
identifyUser,
setUserProperties,
// Session/Screen
trackScreenView,
trackSessionStart,
};
}