feat(analytics): implement total transparency suite and SEO metadata standardization

- Added global ScrollDepthTracker (25%, 50%, 75%, 100%)
- Implemented ProductEngagementTracker for deep product analytics
- Added field-level tracking to ContactForm and RequestQuoteForm
- Standardized SEO metadata (canonical, alternates, x-default) across all routes
- Created reusable TrackedLink and TrackedButton components for server components
- Fixed 'useAnalytics' hook error in Footer.tsx by adding 'use client'
This commit is contained in:
2026-02-16 18:30:29 +01:00
parent c2e790e533
commit 58d83a7fa1
22 changed files with 634 additions and 98 deletions

View File

@@ -0,0 +1,50 @@
'use client';
import { useEffect } from 'react';
import { useAnalytics } from './useAnalytics';
import { AnalyticsEvents } from './analytics-events';
interface ProductEngagementTrackerProps {
productName: string;
productSlug: string;
categories: string[];
sku?: string;
}
/**
* ProductEngagementTracker
* Deep analytics for product pages.
* Tracks specific view events with full metadata for sales analysis.
*/
export default function ProductEngagementTracker({
productName,
productSlug,
categories,
sku,
}: ProductEngagementTrackerProps) {
const { trackEvent } = useAnalytics();
useEffect(() => {
// Standardized product view event for "High-Fidelity" sales insights
trackEvent(AnalyticsEvents.PRODUCT_VIEW, {
product_id: productSlug,
product_name: productName,
product_sku: sku,
product_categories: categories.join(', '),
location: 'pdp_standard',
});
// We can also track "Engagement Start" to measure dwell time later
const startTime = Date.now();
return () => {
const dwellTime = Math.round((Date.now() - startTime) / 1000);
trackEvent('pdp_dwell_time', {
product_id: productSlug,
seconds: dwellTime,
});
};
}, [productName, productSlug, categories, sku, trackEvent]);
return null;
}

View File

@@ -0,0 +1,62 @@
'use client';
import { useEffect, useRef } from 'react';
import { usePathname } from 'next/navigation';
import { useAnalytics } from './useAnalytics';
import { AnalyticsEvents } from './analytics-events';
/**
* ScrollDepthTracker
* Tracks user scroll progress across pages.
* Fires events at 25%, 50%, 75%, and 100% depth.
*/
export default function ScrollDepthTracker() {
const pathname = usePathname();
const { trackEvent } = useAnalytics();
const trackedDepths = useRef<Set<number>>(new Set());
// Reset tracking when path changes
useEffect(() => {
trackedDepths.current.clear();
}, [pathname]);
useEffect(() => {
const handleScroll = () => {
const scrollY = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// Calculate how far the user has scrolled in percentage
// documentHeight - windowHeight is the total scrollable distance
const totalScrollable = documentHeight - windowHeight;
if (totalScrollable <= 0) return; // Not scrollable
const scrollPercentage = Math.round((scrollY / totalScrollable) * 100);
// We only care about specific milestones
const milestones = [25, 50, 75, 100];
milestones.forEach((milestone) => {
if (scrollPercentage >= milestone && !trackedDepths.current.has(milestone)) {
trackedDepths.current.add(milestone);
trackEvent(AnalyticsEvents.SCROLL_DEPTH, {
depth: milestone,
path: pathname,
});
}
});
};
// Use passive listener for better performance
window.addEventListener('scroll', handleScroll, { passive: true });
// Initial check (in case page is short or already scrolled)
handleScroll();
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [pathname, trackEvent]);
return null;
}

View File

@@ -0,0 +1,34 @@
'use client';
import React from 'react';
import { Button, ButtonProps } from '../ui/Button';
import { useAnalytics } from './useAnalytics';
import { AnalyticsEvents } from './analytics-events';
interface TrackedButtonProps extends ButtonProps {
eventName?: string;
eventProperties?: Record<string, any>;
}
/**
* A wrapper around the project's Button component that tracks click events.
* Safe to use in server components.
*/
export default function TrackedButton({
eventName = AnalyticsEvents.BUTTON_CLICK,
eventProperties = {},
onClick,
...props
}: TrackedButtonProps) {
const { trackEvent } = useAnalytics();
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
trackEvent(eventName, {
...eventProperties,
label: typeof props.children === 'string' ? props.children : eventProperties.label,
});
if (onClick) onClick(e);
};
return <Button {...props} onClick={handleClick} />;
}

View File

@@ -0,0 +1,44 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { useAnalytics } from './useAnalytics';
import { AnalyticsEvents } from './analytics-events';
interface TrackedLinkProps {
href: string;
eventName?: string;
eventProperties?: Record<string, any>;
className?: string;
children: React.ReactNode;
onClick?: () => void;
}
/**
* A wrapper around next/link that tracks the click event.
* Useful for adding tracking to server components.
*/
export default function TrackedLink({
href,
eventName = AnalyticsEvents.LINK_CLICK,
eventProperties = {},
className,
children,
onClick,
}: TrackedLinkProps) {
const { trackEvent } = useAnalytics();
const handleClick = (e: React.MouseEvent) => {
trackEvent(eventName, {
href,
...eventProperties,
});
if (onClick) onClick();
};
return (
<Link href={href} className={className} onClick={handleClick}>
{children}
</Link>
);
}

View File

@@ -1,18 +1,18 @@
/**
* Analytics Events Utility
*
*
* Centralized definitions for common analytics events and their properties.
* This helps maintain consistency across the application and makes it easier
* to track meaningful events.
*
*
* @example
* ```tsx
* import { useAnalytics } from '@/components/analytics/useAnalytics';
* import { AnalyticsEvents } from '@/components/analytics/analytics-events';
*
*
* function ProductPage() {
* const { trackEvent } = useAnalytics();
*
*
* const handleAddToCart = (productId: string, productName: string) => {
* trackEvent(AnalyticsEvents.PRODUCT_ADD_TO_CART, {
* product_id: productId,
@@ -20,7 +20,7 @@
* page: 'product-detail'
* });
* };
*
*
* return <button onClick={() => handleAddToCart('123', 'Cable')}>Add to Cart</button>;
* }
* ```
@@ -31,6 +31,7 @@ export const AnalyticsEvents = {
PAGE_VIEW: 'pageview',
PAGE_SCROLL: 'page_scroll',
PAGE_EXIT: 'page_exit',
SCROLL_DEPTH: 'scroll_depth',
// User Interaction Events
BUTTON_CLICK: 'button_click',
@@ -38,6 +39,7 @@ export const AnalyticsEvents = {
FORM_SUBMIT: 'form_submit',
FORM_START: 'form_start',
FORM_ERROR: 'form_error',
FORM_FIELD_FOCUS: 'form_field_focus',
// E-commerce Events
PRODUCT_VIEW: 'product_view',
@@ -46,6 +48,7 @@ export const AnalyticsEvents = {
PRODUCT_PURCHASE: 'product_purchase',
PRODUCT_WISHLIST_ADD: 'product_wishlist_add',
PRODUCT_WISHLIST_REMOVE: 'product_wishlist_remove',
PRODUCT_TAB_SWITCH: 'product_tab_switch',
// Search & Filter Events
SEARCH: 'search',
@@ -71,6 +74,7 @@ export const AnalyticsEvents = {
TOGGLE_SWITCH: 'toggle_switch',
ACCORDION_TOGGLE: 'accordion_toggle',
TAB_SWITCH: 'tab_switch',
TOC_CLICK: 'toc_click',
// Error & Performance Events
ERROR: 'error',