192 lines
4.1 KiB
TypeScript
192 lines
4.1 KiB
TypeScript
|
|
/**
|
||
|
|
* useClickTracking - Click event tracking utilities
|
||
|
|
*
|
||
|
|
* Provides utilities for tracking user interactions with elements.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useCallback } from 'react';
|
||
|
|
import { useAnalytics } from '@analytics/client/react';
|
||
|
|
|
||
|
|
interface ClickTrackingOptions {
|
||
|
|
/**
|
||
|
|
* Element identifier (button name, link text, etc.)
|
||
|
|
*/
|
||
|
|
elementId: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Category for grouping (navigation, cta, form, etc.)
|
||
|
|
*/
|
||
|
|
category?: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Additional metadata
|
||
|
|
*/
|
||
|
|
metadata?: Record<string, unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a click handler that tracks the interaction.
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```tsx
|
||
|
|
* function SignupButton() {
|
||
|
|
* const { createClickHandler } = useClickTracking();
|
||
|
|
*
|
||
|
|
* const handleClick = createClickHandler({
|
||
|
|
* elementId: 'signup-button',
|
||
|
|
* category: 'cta',
|
||
|
|
* });
|
||
|
|
*
|
||
|
|
* return <button onClick={handleClick}>Sign Up Free</button>;
|
||
|
|
* }
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function useClickTracking() {
|
||
|
|
const { trackInteraction } = useAnalytics();
|
||
|
|
|
||
|
|
const createClickHandler = useCallback(
|
||
|
|
(options: ClickTrackingOptions) => {
|
||
|
|
return (event?: React.MouseEvent) => {
|
||
|
|
trackInteraction({
|
||
|
|
type: 'click',
|
||
|
|
data: {
|
||
|
|
elementId: options.elementId,
|
||
|
|
category: options.category,
|
||
|
|
pageUrl: window.location.href,
|
||
|
|
...options.metadata,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
};
|
||
|
|
},
|
||
|
|
[trackInteraction],
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Track a click imperatively (not as an event handler).
|
||
|
|
*/
|
||
|
|
const trackClick = useCallback(
|
||
|
|
(options: ClickTrackingOptions) => {
|
||
|
|
trackInteraction({
|
||
|
|
type: 'click',
|
||
|
|
data: {
|
||
|
|
elementId: options.elementId,
|
||
|
|
category: options.category,
|
||
|
|
pageUrl: window.location.href,
|
||
|
|
...options.metadata,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
},
|
||
|
|
[trackInteraction],
|
||
|
|
);
|
||
|
|
|
||
|
|
return {
|
||
|
|
createClickHandler,
|
||
|
|
trackClick,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Track outbound link clicks.
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```tsx
|
||
|
|
* function ExternalLink({ href, children }) {
|
||
|
|
* const { trackOutboundClick } = useOutboundLinkTracking();
|
||
|
|
*
|
||
|
|
* return (
|
||
|
|
* <a
|
||
|
|
* href={href}
|
||
|
|
* onClick={() => trackOutboundClick(href)}
|
||
|
|
* target="_blank"
|
||
|
|
* rel="noopener noreferrer"
|
||
|
|
* >
|
||
|
|
* {children}
|
||
|
|
* </a>
|
||
|
|
* );
|
||
|
|
* }
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function useOutboundLinkTracking() {
|
||
|
|
const { trackInteraction } = useAnalytics();
|
||
|
|
|
||
|
|
const trackOutboundClick = useCallback(
|
||
|
|
(url: string, metadata?: Record<string, unknown>) => {
|
||
|
|
try {
|
||
|
|
const parsed = new URL(url);
|
||
|
|
trackInteraction({
|
||
|
|
type: 'click',
|
||
|
|
data: {
|
||
|
|
elementId: 'outbound-link',
|
||
|
|
category: 'navigation',
|
||
|
|
targetUrl: url,
|
||
|
|
targetDomain: parsed.hostname,
|
||
|
|
pageUrl: window.location.href,
|
||
|
|
...metadata,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
} catch {
|
||
|
|
// Invalid URL, track anyway
|
||
|
|
trackInteraction({
|
||
|
|
type: 'click',
|
||
|
|
data: {
|
||
|
|
elementId: 'outbound-link',
|
||
|
|
category: 'navigation',
|
||
|
|
targetUrl: url,
|
||
|
|
pageUrl: window.location.href,
|
||
|
|
...metadata,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[trackInteraction],
|
||
|
|
);
|
||
|
|
|
||
|
|
return { trackOutboundClick };
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Track downloads.
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```tsx
|
||
|
|
* function DownloadButton({ fileUrl, fileName }) {
|
||
|
|
* const { trackDownload } = useDownloadTracking();
|
||
|
|
*
|
||
|
|
* return (
|
||
|
|
* <a
|
||
|
|
* href={fileUrl}
|
||
|
|
* download={fileName}
|
||
|
|
* onClick={() => trackDownload(fileName, fileUrl)}
|
||
|
|
* >
|
||
|
|
* Download {fileName}
|
||
|
|
* </a>
|
||
|
|
* );
|
||
|
|
* }
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function useDownloadTracking() {
|
||
|
|
const { trackInteraction } = useAnalytics();
|
||
|
|
|
||
|
|
const trackDownload = useCallback(
|
||
|
|
(fileName: string, fileUrl: string, metadata?: Record<string, unknown>) => {
|
||
|
|
const fileExtension = fileName.split('.').pop()?.toLowerCase() || 'unknown';
|
||
|
|
|
||
|
|
trackInteraction({
|
||
|
|
type: 'click',
|
||
|
|
data: {
|
||
|
|
elementId: 'download',
|
||
|
|
category: 'download',
|
||
|
|
fileName,
|
||
|
|
fileUrl,
|
||
|
|
fileExtension,
|
||
|
|
pageUrl: window.location.href,
|
||
|
|
...metadata,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
},
|
||
|
|
[trackInteraction],
|
||
|
|
);
|
||
|
|
|
||
|
|
return { trackDownload };
|
||
|
|
}
|