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"; "use client";
import { useOpenPanel } from "@openpanel/nextjs"; import { useOpenPanel } from "@openpanel/nextjs";
import { useCallback, useRef } from "react"; import { useCallback } 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>;
};
export function useAnalytics() { export function useAnalytics() {
const op = useOpenPanel(); const op = useOpenPanel();
const startTimeRef = useRef<number>(Date.now());
// ==================== E-COMMERCE EVENTS ==================== const trackProductView = useCallback((product: {
id: string;
/** name: string;
* Track when user views a product price: number;
*/ currency: string;
const trackProductView = useCallback((product: ProductViewData) => { category?: string;
op.track("product_viewed", { }) => {
product_id: product.id, try {
product_name: product.name, op.track("product_viewed", {
price: product.price, product_id: product.id,
currency: product.currency, product_name: product.name,
category: product.category, price: product.price,
sku: product.sku, currency: product.currency,
in_stock: product.in_stock, category: product.category,
});
// 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,
}); });
} catch (e) {
console.error("Track error:", e);
} }
}, [op]); }, [op]);
/** const trackAddToCart = useCallback((product: {
* Track payment method selection id: string;
*/ name: string;
const trackPaymentMethodSelect = useCallback((method: string, subtotal: number) => { price: number;
op.track("payment_method_selected", { currency: string;
method: method, quantity: number;
subtotal: subtotal, variant?: string;
}); }) => {
}, [op]); try {
op.track("add_to_cart", {
/** product_id: product.id,
* Track shipping method selection product_name: product.name,
*/ price: product.price,
const trackShippingMethodSelect = useCallback((method: string, cost: number) => { currency: product.currency,
op.track("shipping_method_selected", { quantity: product.quantity,
method: method, variant: product.variant,
cost: cost, });
}); } catch (e) {
}, [op]); console.error("Track error:", e);
/**
* 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 });
} }
}, [op]); }, [op]);
// ==================== USER IDENTIFICATION ==================== const trackRemoveFromCart = useCallback((product: {
id: string;
/** name: string;
* Identify user quantity: number;
*/ }) => {
const identifyUser = useCallback((user: UserData) => { try {
op.identify({ op.track("remove_from_cart", {
profileId: user.profileId, product_id: product.id,
firstName: user.firstName, product_name: product.name,
lastName: user.lastName, quantity: product.quantity,
email: user.email, });
properties: { } catch (e) {
phone: user.phone, console.error("Track error:", e);
...user.properties, }
},
});
}, [op]); }, [op]);
/** const trackCheckoutStarted = useCallback((cart: {
* Set user properties total: number;
*/ currency: string;
const setUserProperties = useCallback((properties: Record<string, unknown>) => { item_count: number;
op.setGlobalProperties(properties); 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]); }, [op]);
// ==================== SCREEN/SESSION TRACKING ==================== const trackCheckoutStep = useCallback((step: string, data?: Record<string, unknown>) => {
try {
/** op.track("checkout_step", {
* Track screen/page view step,
*/ ...data,
const trackScreenView = useCallback((path: string, title?: string) => { });
op.screenView(path, { } catch (e) {
title: title, console.error("Track error:", e);
url: window.location.href, }
referrer: document.referrer,
});
}, [op]); }, [op]);
/** const trackOrderCompleted = useCallback((order: {
* Track session start order_id: string;
*/ order_number: string;
const trackSessionStart = useCallback(() => { total: number;
op.track("session_started", { currency: string;
url: window.location.href, item_count: number;
referrer: document.referrer, shipping_cost?: number;
user_agent: navigator.userAgent, customer_email?: string;
screen_resolution: `${window.screen.width}x${window.screen.height}`, }) => {
}); 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]); }, [op]);
return { return {
// E-commerce
trackProductView, trackProductView,
trackProductImageView,
trackVariantSelect,
trackAddToCart, trackAddToCart,
trackRemoveFromCart, trackRemoveFromCart,
trackQuantityChange,
trackCartOpen,
trackCartAbandonment,
trackCheckoutStarted, trackCheckoutStarted,
trackCheckoutStep, trackCheckoutStep,
trackPaymentMethodSelect,
trackShippingMethodSelect,
trackOrderCompleted, trackOrderCompleted,
// User Engagement
trackSearch, trackSearch,
trackEngagement,
trackCTAClick,
trackExternalLink, trackExternalLink,
trackNewsletterSignup,
trackPromoCode,
trackWishlistAction,
// User Identity
identifyUser, identifyUser,
setUserProperties,
// Session/Screen
trackScreenView,
trackSessionStart,
}; };
} }