feat: Implement comprehensive OpenPanel analytics tracking
Complete analytics overhaul with 30+ tracking events: E-commerce Events: - Product views, image views, variant selection - Add/remove from cart, quantity changes - Cart open and abandonment tracking - Checkout funnel (all steps) - Payment/shipping method selection - Order completion with revenue tracking User Engagement: - Search queries with filters - CTA clicks, external link clicks - Element engagement (click/hover/view) - Newsletter signups - Promo code usage - Wishlist actions User Identity: - User identification - Property setting - Screen/session tracking Technical: - Proper TypeScript types for all events - Increment/decrement counters - Pending revenue for cart abandonment - Comprehensive error handling Includes complete documentation in docs/ANALYTICS_GUIDE.md
This commit is contained in:
388
docs/ANALYTICS_GUIDE.md
Normal file
388
docs/ANALYTICS_GUIDE.md
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
# Comprehensive OpenPanel Analytics Guide
|
||||||
|
|
||||||
|
This guide documents all tracking events implemented in the ManoonOils storefront.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useAnalytics } from "@/lib/analytics";
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const { trackProductView, trackAddToCart, trackOrderCompleted } = useAnalytics();
|
||||||
|
|
||||||
|
// Use tracking functions...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E-Commerce Events
|
||||||
|
|
||||||
|
### 1. Product Views
|
||||||
|
|
||||||
|
**trackProductView** - Track when user views a product
|
||||||
|
```typescript
|
||||||
|
trackProductView({
|
||||||
|
id: "prod_123",
|
||||||
|
name: "Manoon Anti-Age Serum",
|
||||||
|
price: 2890,
|
||||||
|
currency: "RSD",
|
||||||
|
category: "Serums",
|
||||||
|
sku: "MAN-001",
|
||||||
|
in_stock: true,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackProductImageView** - Track product image gallery interactions
|
||||||
|
```typescript
|
||||||
|
trackProductImageView("prod_123", 2); // Viewed 3rd image
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackVariantSelect** - Track variant/option selection
|
||||||
|
```typescript
|
||||||
|
trackVariantSelect("prod_123", "50ml", 2890);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Cart Events
|
||||||
|
|
||||||
|
**trackAddToCart** - Track adding items to cart
|
||||||
|
```typescript
|
||||||
|
trackAddToCart({
|
||||||
|
id: "prod_123",
|
||||||
|
name: "Manoon Anti-Age Serum",
|
||||||
|
price: 2890,
|
||||||
|
currency: "RSD",
|
||||||
|
quantity: 2,
|
||||||
|
variant: "50ml",
|
||||||
|
sku: "MAN-001-50",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackRemoveFromCart** - Track removing items from cart
|
||||||
|
```typescript
|
||||||
|
trackRemoveFromCart({
|
||||||
|
id: "prod_123",
|
||||||
|
name: "Manoon Anti-Age Serum",
|
||||||
|
price: 2890,
|
||||||
|
quantity: 1,
|
||||||
|
variant: "50ml",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackQuantityChange** - Track quantity adjustments
|
||||||
|
```typescript
|
||||||
|
trackQuantityChange(
|
||||||
|
cartItem,
|
||||||
|
1, // old quantity
|
||||||
|
3 // new quantity
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackCartOpen** - Track cart drawer/modal open
|
||||||
|
```typescript
|
||||||
|
trackCartOpen({
|
||||||
|
total: 5780,
|
||||||
|
currency: "RSD",
|
||||||
|
item_count: 2,
|
||||||
|
items: [/* cart items */],
|
||||||
|
coupon_code: "SAVE10",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackCartAbandonment** - Track cart abandonment
|
||||||
|
```typescript
|
||||||
|
trackCartAbandonment(
|
||||||
|
cartData,
|
||||||
|
45000 // time spent in cart (ms)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Checkout Events
|
||||||
|
|
||||||
|
**trackCheckoutStarted** - Track checkout initiation
|
||||||
|
```typescript
|
||||||
|
trackCheckoutStarted({
|
||||||
|
total: 5780,
|
||||||
|
currency: "RSD",
|
||||||
|
item_count: 2,
|
||||||
|
items: [/* cart items */],
|
||||||
|
coupon_code: "SAVE10",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackCheckoutStep** - Track checkout step progression
|
||||||
|
```typescript
|
||||||
|
// Step progression
|
||||||
|
trackCheckoutStep({
|
||||||
|
step: "email",
|
||||||
|
value: 5780,
|
||||||
|
currency: "RSD",
|
||||||
|
});
|
||||||
|
|
||||||
|
// With error
|
||||||
|
trackCheckoutStep({
|
||||||
|
step: "shipping",
|
||||||
|
error: "Invalid postal code",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Final step
|
||||||
|
trackCheckoutStep({
|
||||||
|
step: "complete",
|
||||||
|
payment_method: "cod",
|
||||||
|
shipping_method: "Standard",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackPaymentMethodSelect** - Track payment method selection
|
||||||
|
```typescript
|
||||||
|
trackPaymentMethodSelect("cod", 5780);
|
||||||
|
```
|
||||||
|
|
||||||
|
**trackShippingMethodSelect** - Track shipping method selection
|
||||||
|
```typescript
|
||||||
|
trackShippingMethodSelect("Standard", 480);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Order Events
|
||||||
|
|
||||||
|
**trackOrderCompleted** - Track successful order with revenue
|
||||||
|
```typescript
|
||||||
|
trackOrderCompleted({
|
||||||
|
order_id: "order_uuid",
|
||||||
|
order_number: "1599",
|
||||||
|
total: 6260,
|
||||||
|
currency: "RSD",
|
||||||
|
item_count: 2,
|
||||||
|
shipping_cost: 480,
|
||||||
|
customer_email: "customer@example.com",
|
||||||
|
payment_method: "cod",
|
||||||
|
coupon_code: "SAVE10",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## User Engagement Events
|
||||||
|
|
||||||
|
### 1. Search
|
||||||
|
|
||||||
|
**trackSearch** - Track search queries
|
||||||
|
```typescript
|
||||||
|
trackSearch({
|
||||||
|
query: "anti aging serum",
|
||||||
|
results_count: 12,
|
||||||
|
filters: { category: "serums", price_range: "2000-3000" },
|
||||||
|
category: "serums",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. General Engagement
|
||||||
|
|
||||||
|
**trackEngagement** - Track element interactions
|
||||||
|
```typescript
|
||||||
|
// Element click
|
||||||
|
trackEngagement({
|
||||||
|
element: "hero_cta",
|
||||||
|
action: "click",
|
||||||
|
value: "Shop Now",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Element hover
|
||||||
|
trackEngagement({
|
||||||
|
element: "product_card",
|
||||||
|
action: "hover",
|
||||||
|
value: "prod_123",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Element view (scroll into view)
|
||||||
|
trackEngagement({
|
||||||
|
element: "testimonials_section",
|
||||||
|
action: "view",
|
||||||
|
metadata: { section_position: "below_fold" },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. CTA Tracking
|
||||||
|
|
||||||
|
**trackCTAClick** - Track call-to-action buttons
|
||||||
|
```typescript
|
||||||
|
trackCTAClick(
|
||||||
|
"Shop Now", // CTA name
|
||||||
|
"hero_section", // Location
|
||||||
|
"/products" // Destination (optional)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. External Links
|
||||||
|
|
||||||
|
**trackExternalLink** - Track outbound links
|
||||||
|
```typescript
|
||||||
|
trackExternalLink(
|
||||||
|
"https://instagram.com/manoonoils",
|
||||||
|
"Instagram",
|
||||||
|
"footer"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Newsletter
|
||||||
|
|
||||||
|
**trackNewsletterSignup** - Track email subscriptions
|
||||||
|
```typescript
|
||||||
|
trackNewsletterSignup(
|
||||||
|
"customer@example.com",
|
||||||
|
"footer" // Location of signup form
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Promo Codes
|
||||||
|
|
||||||
|
**trackPromoCode** - Track coupon/promo code usage
|
||||||
|
```typescript
|
||||||
|
trackPromoCode(
|
||||||
|
"SAVE10",
|
||||||
|
578, // discount amount
|
||||||
|
true // success
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Wishlist
|
||||||
|
|
||||||
|
**trackWishlistAction** - Track wishlist interactions
|
||||||
|
```typescript
|
||||||
|
// Add to wishlist
|
||||||
|
trackWishlistAction("add", "prod_123", "Anti-Age Serum");
|
||||||
|
|
||||||
|
// Remove from wishlist
|
||||||
|
trackWishlistAction("remove", "prod_123", "Anti-Age Serum");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## User Identification
|
||||||
|
|
||||||
|
### identifyUser
|
||||||
|
|
||||||
|
Identify users across sessions:
|
||||||
|
```typescript
|
||||||
|
identifyUser({
|
||||||
|
profileId: "user_uuid",
|
||||||
|
email: "customer@example.com",
|
||||||
|
firstName: "John",
|
||||||
|
lastName: "Doe",
|
||||||
|
phone: "+38161123456",
|
||||||
|
properties: {
|
||||||
|
signup_date: "2024-03-01",
|
||||||
|
preferred_language: "sr",
|
||||||
|
total_orders: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### setUserProperties
|
||||||
|
|
||||||
|
Set global user properties:
|
||||||
|
```typescript
|
||||||
|
setUserProperties({
|
||||||
|
loyalty_tier: "gold",
|
||||||
|
last_purchase_date: "2024-03-25",
|
||||||
|
preferred_category: "serums",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session/Screen Tracking
|
||||||
|
|
||||||
|
### trackScreenView
|
||||||
|
|
||||||
|
Track page views manually:
|
||||||
|
```typescript
|
||||||
|
trackScreenView(
|
||||||
|
"/products/anti-age-serum",
|
||||||
|
"Manoon Anti-Age Serum - ManoonOils"
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### trackSessionStart
|
||||||
|
|
||||||
|
Track new sessions:
|
||||||
|
```typescript
|
||||||
|
useEffect(() => {
|
||||||
|
trackSessionStart();
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Always Wrap in try-catch
|
||||||
|
Tracking should never break the user experience:
|
||||||
|
```typescript
|
||||||
|
try {
|
||||||
|
trackAddToCart(product);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Tracking failed:", e);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Use Consistent Naming
|
||||||
|
- Use snake_case for property names
|
||||||
|
- Be consistent with event names
|
||||||
|
- Use past tense for events (e.g., `product_viewed` not `view_product`)
|
||||||
|
|
||||||
|
### 3. Include Context
|
||||||
|
Always include relevant context:
|
||||||
|
```typescript
|
||||||
|
// Good
|
||||||
|
trackCTAClick("Shop Now", "hero_section", "/products");
|
||||||
|
|
||||||
|
// Less useful
|
||||||
|
trackCTAClick("button_click");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Track Revenue Properly
|
||||||
|
Always use `trackOrderCompleted` for final purchases - it includes both event tracking and revenue tracking.
|
||||||
|
|
||||||
|
### 5. Increment/Decrement Counters
|
||||||
|
Use increment/decrement for user-level metrics:
|
||||||
|
- Total orders: `op.increment({ total_orders: 1 })`
|
||||||
|
- Wishlist items: `op.increment({ wishlist_items: 1 })`
|
||||||
|
- Product views: `op.increment({ product_views: 1 })`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Analytics Dashboard Views
|
||||||
|
|
||||||
|
With this implementation, you can create OpenPanel dashboards for:
|
||||||
|
|
||||||
|
1. **E-commerce Funnel**
|
||||||
|
- Product views → Add to cart → Checkout started → Order completed
|
||||||
|
- Conversion rates at each step
|
||||||
|
- Cart abandonment rate
|
||||||
|
|
||||||
|
2. **Revenue Analytics**
|
||||||
|
- Total revenue by period
|
||||||
|
- Revenue by payment method
|
||||||
|
- Revenue by product category
|
||||||
|
- Average order value
|
||||||
|
|
||||||
|
3. **User Behavior**
|
||||||
|
- Most viewed products
|
||||||
|
- Popular search terms
|
||||||
|
- CTA click rates
|
||||||
|
- Time to purchase
|
||||||
|
|
||||||
|
4. **User Properties**
|
||||||
|
- User segments by total orders
|
||||||
|
- Repeat customers
|
||||||
|
- Newsletter subscribers
|
||||||
|
- Wishlist users
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
Check browser console for tracking logs. All tracking functions log to console in development mode.
|
||||||
|
|
||||||
|
OpenPanel dashboard: https://op.nodecrew.me
|
||||||
@@ -1,95 +1,282 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useOpenPanel } from "@openpanel/nextjs";
|
import { useOpenPanel } from "@openpanel/nextjs";
|
||||||
import { useCallback } from "react";
|
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>;
|
||||||
|
};
|
||||||
|
|
||||||
export function useAnalytics() {
|
export function useAnalytics() {
|
||||||
const op = useOpenPanel();
|
const op = useOpenPanel();
|
||||||
|
const startTimeRef = useRef<number>(Date.now());
|
||||||
|
|
||||||
// Page views are tracked automatically by OpenPanelComponent
|
// ==================== E-COMMERCE EVENTS ====================
|
||||||
// but we can track specific events manually
|
|
||||||
|
|
||||||
const trackProductView = useCallback((product: {
|
/**
|
||||||
id: string;
|
* Track when user views a product
|
||||||
name: string;
|
*/
|
||||||
price: number;
|
const trackProductView = useCallback((product: ProductViewData) => {
|
||||||
currency: string;
|
|
||||||
category?: string;
|
|
||||||
}) => {
|
|
||||||
op.track("product_viewed", {
|
op.track("product_viewed", {
|
||||||
product_id: product.id,
|
product_id: product.id,
|
||||||
product_name: product.name,
|
product_name: product.name,
|
||||||
price: product.price,
|
price: product.price,
|
||||||
currency: product.currency,
|
currency: product.currency,
|
||||||
category: product.category,
|
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]);
|
}, [op]);
|
||||||
|
|
||||||
const trackAddToCart = useCallback((product: {
|
/**
|
||||||
id: string;
|
* Track variant selection
|
||||||
name: string;
|
*/
|
||||||
price: number;
|
const trackVariantSelect = useCallback((productId: string, variantName: string, price: number) => {
|
||||||
currency: string;
|
op.track("variant_selected", {
|
||||||
quantity: number;
|
product_id: productId,
|
||||||
variant?: string;
|
variant_name: variantName,
|
||||||
}) => {
|
price: price,
|
||||||
|
});
|
||||||
|
}, [op]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track add to cart
|
||||||
|
*/
|
||||||
|
const trackAddToCart = useCallback((item: CartItemData) => {
|
||||||
op.track("add_to_cart", {
|
op.track("add_to_cart", {
|
||||||
product_id: product.id,
|
product_id: item.id,
|
||||||
product_name: product.name,
|
product_name: item.name,
|
||||||
price: product.price,
|
price: item.price,
|
||||||
currency: product.currency,
|
currency: item.currency,
|
||||||
quantity: product.quantity,
|
quantity: item.quantity,
|
||||||
variant: product.variant,
|
variant: item.variant,
|
||||||
|
sku: item.sku,
|
||||||
|
value: item.price * item.quantity,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add to cart counter
|
||||||
|
op.increment({ items_added_to_cart: item.quantity });
|
||||||
}, [op]);
|
}, [op]);
|
||||||
|
|
||||||
const trackRemoveFromCart = useCallback((product: {
|
/**
|
||||||
id: string;
|
* Track remove from cart
|
||||||
name: string;
|
*/
|
||||||
quantity: number;
|
const trackRemoveFromCart = useCallback((item: CartItemData) => {
|
||||||
}) => {
|
|
||||||
op.track("remove_from_cart", {
|
op.track("remove_from_cart", {
|
||||||
product_id: product.id,
|
product_id: item.id,
|
||||||
product_name: product.name,
|
product_name: item.name,
|
||||||
quantity: product.quantity,
|
price: item.price,
|
||||||
|
quantity: item.quantity,
|
||||||
|
variant: item.variant,
|
||||||
|
value: item.price * item.quantity,
|
||||||
});
|
});
|
||||||
}, [op]);
|
}, [op]);
|
||||||
|
|
||||||
const trackCheckoutStarted = useCallback((cart: {
|
/**
|
||||||
total: number;
|
* Track quantity change in cart
|
||||||
currency: string;
|
*/
|
||||||
item_count: number;
|
const trackQuantityChange = useCallback((item: CartItemData, oldQuantity: number, newQuantity: number) => {
|
||||||
items: Array<{
|
op.track("quantity_changed", {
|
||||||
id: string;
|
product_id: item.id,
|
||||||
name: string;
|
product_name: item.name,
|
||||||
quantity: number;
|
old_quantity: oldQuantity,
|
||||||
price: number;
|
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", {
|
op.track("checkout_started", {
|
||||||
cart_total: cart.total,
|
cart_total: cart.total,
|
||||||
currency: cart.currency,
|
currency: cart.currency,
|
||||||
item_count: cart.item_count,
|
item_count: cart.item_count,
|
||||||
items: cart.items,
|
items: cart.items.map(i => ({
|
||||||
|
id: i.id,
|
||||||
|
name: i.name,
|
||||||
|
quantity: i.quantity,
|
||||||
|
price: i.price,
|
||||||
|
})),
|
||||||
|
coupon_code: cart.coupon_code,
|
||||||
});
|
});
|
||||||
}, [op]);
|
}, [op]);
|
||||||
|
|
||||||
const trackCheckoutStep = useCallback((step: string, data?: Record<string, unknown>) => {
|
/**
|
||||||
op.track("checkout_step", {
|
* Track checkout step progression
|
||||||
step,
|
*/
|
||||||
...data,
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [op]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track payment method selection
|
||||||
|
*/
|
||||||
|
const trackPaymentMethodSelect = useCallback((method: string, subtotal: number) => {
|
||||||
|
op.track("payment_method_selected", {
|
||||||
|
method: method,
|
||||||
|
subtotal: subtotal,
|
||||||
});
|
});
|
||||||
}, [op]);
|
}, [op]);
|
||||||
|
|
||||||
const trackOrderCompleted = useCallback((order: {
|
/**
|
||||||
order_id: string;
|
* Track shipping method selection
|
||||||
order_number: string;
|
*/
|
||||||
total: number;
|
const trackShippingMethodSelect = useCallback((method: string, cost: number) => {
|
||||||
currency: string;
|
op.track("shipping_method_selected", {
|
||||||
item_count: number;
|
method: method,
|
||||||
shipping_cost?: number;
|
cost: cost,
|
||||||
customer_email?: string;
|
});
|
||||||
}) => {
|
}, [op]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track order completion with revenue
|
||||||
|
*/
|
||||||
|
const trackOrderCompleted = useCallback((order: OrderData) => {
|
||||||
|
const timeToComplete = Date.now() - startTimeRef.current;
|
||||||
|
|
||||||
|
// Track order event
|
||||||
op.track("order_completed", {
|
op.track("order_completed", {
|
||||||
order_id: order.order_id,
|
order_id: order.order_id,
|
||||||
order_number: order.order_number,
|
order_number: order.order_number,
|
||||||
@@ -98,56 +285,196 @@ export function useAnalytics() {
|
|||||||
item_count: order.item_count,
|
item_count: order.item_count,
|
||||||
shipping_cost: order.shipping_cost,
|
shipping_cost: order.shipping_cost,
|
||||||
customer_email: order.customer_email,
|
customer_email: order.customer_email,
|
||||||
|
payment_method: order.payment_method,
|
||||||
|
coupon_code: order.coupon_code,
|
||||||
|
time_to_complete_ms: timeToComplete,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also track revenue for analytics
|
// Track actual revenue
|
||||||
op.track("purchase", {
|
op.revenue(order.total, {
|
||||||
transaction_id: order.order_number,
|
|
||||||
value: order.total,
|
|
||||||
currency: order.currency,
|
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]);
|
}, [op]);
|
||||||
|
|
||||||
const trackSearch = useCallback((query: string, results_count: number) => {
|
// ==================== USER ENGAGEMENT EVENTS ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track search queries
|
||||||
|
*/
|
||||||
|
const trackSearch = useCallback((data: SearchData) => {
|
||||||
op.track("search", {
|
op.track("search", {
|
||||||
query,
|
query: data.query,
|
||||||
results_count,
|
results_count: data.results_count,
|
||||||
|
filters: data.filters,
|
||||||
|
category: data.category,
|
||||||
});
|
});
|
||||||
}, [op]);
|
}, [op]);
|
||||||
|
|
||||||
const trackExternalLink = useCallback((url: string, label?: string) => {
|
/**
|
||||||
|
* 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", {
|
op.track("external_link_click", {
|
||||||
url,
|
url: url,
|
||||||
label,
|
label: label,
|
||||||
|
location: location,
|
||||||
});
|
});
|
||||||
}, [op]);
|
}, [op]);
|
||||||
|
|
||||||
const identifyUser = useCallback((user: {
|
/**
|
||||||
profileId: string;
|
* Track newsletter signup
|
||||||
email?: string;
|
*/
|
||||||
firstName?: string;
|
const trackNewsletterSignup = useCallback((email: string, location: string) => {
|
||||||
lastName?: string;
|
op.track("newsletter_signup", {
|
||||||
properties?: Record<string, unknown>;
|
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]);
|
||||||
|
|
||||||
|
// ==================== USER IDENTIFICATION ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify user
|
||||||
|
*/
|
||||||
|
const identifyUser = useCallback((user: UserData) => {
|
||||||
op.identify({
|
op.identify({
|
||||||
profileId: user.profileId,
|
profileId: user.profileId,
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
properties: user.properties,
|
properties: {
|
||||||
|
phone: user.phone,
|
||||||
|
...user.properties,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [op]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user properties
|
||||||
|
*/
|
||||||
|
const setUserProperties = useCallback((properties: Record<string, unknown>) => {
|
||||||
|
op.setGlobalProperties(properties);
|
||||||
|
}, [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,
|
||||||
|
});
|
||||||
|
}, [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}`,
|
||||||
});
|
});
|
||||||
}, [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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user