228 lines
6.6 KiB
TypeScript
228 lines
6.6 KiB
TypeScript
/**
|
|
* useCartTracking - React hook for cart analytics
|
|
*
|
|
* Integrates with your cart state to automatically track cart events.
|
|
*/
|
|
|
|
import { useCallback, useEffect, useRef } from 'react';
|
|
|
|
import { useAnalytics } from '../react-spa/analytics-setup';
|
|
|
|
import {
|
|
trackCartAdd,
|
|
trackCartRemove,
|
|
trackCartUpdate,
|
|
trackCartView,
|
|
trackCheckoutStart,
|
|
trackCartAbandonment,
|
|
trackPromoCodeApply,
|
|
type Cart,
|
|
} from './cart-analytics';
|
|
import type { Product } from './product-analytics';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Hook
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
interface UseCartTrackingOptions {
|
|
/** Track cart view automatically when cart changes */
|
|
trackViewOnChange?: boolean;
|
|
/** Track abandonment on page unload */
|
|
trackAbandonmentOnUnload?: boolean;
|
|
}
|
|
|
|
export function useCartTracking(cart: Cart, options: UseCartTrackingOptions = {}) {
|
|
const { trackViewOnChange = false, trackAbandonmentOnUnload = true } = options;
|
|
const { client } = useAnalytics();
|
|
const previousCartRef = useRef<Cart | null>(null);
|
|
|
|
// Track cart view when cart contents change
|
|
useEffect(() => {
|
|
if (!trackViewOnChange) return;
|
|
|
|
const prevItemCount = previousCartRef.current?.items.length ?? 0;
|
|
const currItemCount = cart.items.length;
|
|
|
|
// Only track if items actually changed
|
|
if (prevItemCount !== currItemCount) {
|
|
trackCartView(client, cart);
|
|
}
|
|
|
|
previousCartRef.current = cart;
|
|
}, [client, cart, trackViewOnChange]);
|
|
|
|
// Track abandonment on page unload
|
|
useEffect(() => {
|
|
if (!trackAbandonmentOnUnload) return;
|
|
if (cart.items.length === 0) return;
|
|
|
|
const handleUnload = () => {
|
|
trackCartAbandonment(client, cart, 'browser_close');
|
|
};
|
|
|
|
window.addEventListener('beforeunload', handleUnload);
|
|
return () => window.removeEventListener('beforeunload', handleUnload);
|
|
}, [client, cart, trackAbandonmentOnUnload]);
|
|
|
|
/**
|
|
* Track item added to cart.
|
|
*/
|
|
const trackAdd = useCallback(
|
|
(product: Product, quantity: number) => {
|
|
trackCartAdd(client, product, quantity, cart);
|
|
},
|
|
[client, cart],
|
|
);
|
|
|
|
/**
|
|
* Track item removed from cart.
|
|
*/
|
|
const trackRemove = useCallback(
|
|
(product: Product, quantity: number) => {
|
|
trackCartRemove(client, product, quantity, cart);
|
|
},
|
|
[client, cart],
|
|
);
|
|
|
|
/**
|
|
* Track item quantity update.
|
|
*/
|
|
const trackUpdate = useCallback(
|
|
(product: Product, previousQuantity: number, newQuantity: number) => {
|
|
trackCartUpdate(client, product, previousQuantity, newQuantity, cart);
|
|
},
|
|
[client, cart],
|
|
);
|
|
|
|
/**
|
|
* Track cart page view.
|
|
*/
|
|
const trackView = useCallback(() => {
|
|
trackCartView(client, cart);
|
|
}, [client, cart]);
|
|
|
|
/**
|
|
* Track checkout initiation.
|
|
*/
|
|
const trackCheckout = useCallback(() => {
|
|
trackCheckoutStart(client, cart);
|
|
}, [client, cart]);
|
|
|
|
/**
|
|
* Track cart abandonment.
|
|
*/
|
|
const trackAbandonment = useCallback(
|
|
(reason?: 'navigation' | 'timeout' | 'browser_close') => {
|
|
trackCartAbandonment(client, cart, reason);
|
|
},
|
|
[client, cart],
|
|
);
|
|
|
|
/**
|
|
* Track promo code application.
|
|
*/
|
|
const trackPromoCode = useCallback(
|
|
(code: string, success: boolean, discount?: number, errorMessage?: string) => {
|
|
trackPromoCodeApply(client, code, success, discount, errorMessage);
|
|
},
|
|
[client],
|
|
);
|
|
|
|
return {
|
|
trackAdd,
|
|
trackRemove,
|
|
trackUpdate,
|
|
trackView,
|
|
trackCheckout,
|
|
trackAbandonment,
|
|
trackPromoCode,
|
|
};
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Cart Provider Integration Example
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* @example
|
|
* ```tsx
|
|
* // CartProvider.tsx - Integrate tracking with your cart state
|
|
*
|
|
* import { createContext, useContext, useState, useMemo } from 'react';
|
|
* import { useCartTracking } from './use-cart-tracking';
|
|
*
|
|
* const CartContext = createContext(null);
|
|
*
|
|
* export function CartProvider({ children }) {
|
|
* const [cart, setCart] = useState({ items: [], subtotal: 0, currency: 'USD' });
|
|
* const tracking = useCartTracking(cart, {
|
|
* trackAbandonmentOnUnload: true,
|
|
* });
|
|
*
|
|
* const addItem = (product, quantity) => {
|
|
* setCart(prev => {
|
|
* const newCart = addToCart(prev, product, quantity);
|
|
* // Track after state update
|
|
* tracking.trackAdd(product, quantity);
|
|
* return newCart;
|
|
* });
|
|
* };
|
|
*
|
|
* const removeItem = (product) => {
|
|
* const item = cart.items.find(i => i.id === product.id);
|
|
* if (!item) return;
|
|
*
|
|
* setCart(prev => {
|
|
* const newCart = removeFromCart(prev, product.id);
|
|
* tracking.trackRemove(product, item.quantity);
|
|
* return newCart;
|
|
* });
|
|
* };
|
|
*
|
|
* const updateQuantity = (product, newQuantity) => {
|
|
* const item = cart.items.find(i => i.id === product.id);
|
|
* if (!item) return;
|
|
*
|
|
* const previousQuantity = item.quantity;
|
|
*
|
|
* setCart(prev => {
|
|
* const newCart = updateCartItem(prev, product.id, newQuantity);
|
|
* tracking.trackUpdate(product, previousQuantity, newQuantity);
|
|
* return newCart;
|
|
* });
|
|
* };
|
|
*
|
|
* const startCheckout = () => {
|
|
* tracking.trackCheckout();
|
|
* // Navigate to checkout...
|
|
* };
|
|
*
|
|
* const applyPromoCode = async (code) => {
|
|
* try {
|
|
* const result = await validatePromoCode(code);
|
|
* setCart(prev => applyDiscount(prev, result.discount));
|
|
* tracking.trackPromoCode(code, true, result.discount);
|
|
* } catch (error) {
|
|
* tracking.trackPromoCode(code, false, undefined, error.message);
|
|
* }
|
|
* };
|
|
*
|
|
* const value = useMemo(() => ({
|
|
* cart,
|
|
* addItem,
|
|
* removeItem,
|
|
* updateQuantity,
|
|
* startCheckout,
|
|
* applyPromoCode,
|
|
* }), [cart]);
|
|
*
|
|
* return (
|
|
* <CartContext.Provider value={value}>
|
|
* {children}
|
|
* </CartContext.Provider>
|
|
* );
|
|
* }
|
|
*
|
|
* export const useCart = () => useContext(CartContext);
|
|
* ```
|
|
*/
|