refactor tracking to ues callbacks instead of refs

This commit is contained in:
2025-11-12 10:50:00 +01:00
parent fd17ea0620
commit 18d935270f
3 changed files with 143 additions and 134 deletions

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import type { EventName } from '@/lib/events'; import type { EventName } from '@/lib/events';
import { useHoverTracking } from '@/hooks/useHoverTracking';
const dispatchInteraction = (eventName: EventName, productId?: string, metadata?: Record<string, unknown>) => { const dispatchInteraction = (eventName: EventName, productId?: string, metadata?: Record<string, unknown>) => {
const e = new CustomEvent('definedInteraction', { const e = new CustomEvent('definedInteraction', {
@@ -29,8 +30,20 @@ const PriceDisplay = ({ price }: { price: number }) => (
); );
export default function AirlineCard({ flight }: { flight: Flight }) { export default function AirlineCard({ flight }: { flight: Flight }) {
const durationRef = useHoverTracking({
eventName: 'hover_over_title',
productId: flight.id,
metadata: { elementText: flight.duration },
});
const priceRef = useHoverTracking({
eventName: 'hover_over_paragraph',
productId: flight.id,
metadata: { elementText: `$${flight.basePrice}` },
});
const handleCardClick = () => { const handleCardClick = () => {
dispatchInteraction('product_view', flight.id, { dispatchInteraction('view_item_page', flight.id, {
cabinClass: flight.cabinClass, cabinClass: flight.cabinClass,
fareRule: flight.fareRule, fareRule: flight.fareRule,
price: flight.basePrice, price: flight.basePrice,
@@ -41,7 +54,6 @@ export default function AirlineCard({ flight }: { flight: Flight }) {
<div <div
className="flight-card cursor-pointer" className="flight-card cursor-pointer"
onClick={handleCardClick} onClick={handleCardClick}
onMouseEnter={() => dispatchInteraction('product_hover', flight.id)}
> >
<div className="flight-timing"> <div className="flight-timing">
<div className="flight-time">{flight.departure.time}</div> <div className="flight-time">{flight.departure.time}</div>
@@ -49,7 +61,7 @@ export default function AirlineCard({ flight }: { flight: Flight }) {
</div> </div>
<div className="flight-route"> <div className="flight-route">
<div className="flight-duration">{flight.duration}</div> <div ref={durationRef} className="flight-duration">{flight.duration}</div>
<div className="flight-stops"> <div className="flight-stops">
{flight.stops === 0 ? 'Direct' : `${flight.stops} stop${flight.stops > 1 ? 's' : ''}`} {flight.stops === 0 ? 'Direct' : `${flight.stops} stop${flight.stops > 1 ? 's' : ''}`}
</div> </div>
@@ -66,8 +78,10 @@ export default function AirlineCard({ flight }: { flight: Flight }) {
{flight.refundable && ( {flight.refundable && (
<div className="badge-value text-xs mb-2">Refundable</div> <div className="badge-value text-xs mb-2">Refundable</div>
)} )}
<div ref={priceRef}>
<PriceDisplay price={flight.basePrice} /> <PriceDisplay price={flight.basePrice} />
</div> </div>
</div> </div>
</div>
); );
} }

View File

@@ -1,6 +1,7 @@
'use client'; 'use client';
import type { EventName } from '@/lib/events'; import type { EventName } from '@/lib/events';
import { useHoverTracking } from '@/hooks/useHoverTracking';
const dispatchInteraction = (eventName: EventName, productId?: string, metadata?: Record<string, unknown>) => { const dispatchInteraction = (eventName: EventName, productId?: string, metadata?: Record<string, unknown>) => {
const e = new CustomEvent('definedInteraction', { const e = new CustomEvent('definedInteraction', {
@@ -42,8 +43,20 @@ const AmenityIcon = ({ name }: { name: string }) => {
}; };
export default function HotelCard({ hotel }: { hotel: Hotel }) { export default function HotelCard({ hotel }: { hotel: Hotel }) {
const titleRef = useHoverTracking({
eventName: 'hover_over_title',
productId: hotel.id,
metadata: { elementText: hotel.name },
});
const priceRef = useHoverTracking({
eventName: 'hover_over_paragraph',
productId: hotel.id,
metadata: { elementText: `$${hotel.pricePerNight}` },
});
const handleCardClick = () => { const handleCardClick = () => {
dispatchInteraction('product_view', hotel.id, { dispatchInteraction('view_item_page', hotel.id, {
roomType: hotel.roomType, roomType: hotel.roomType,
price: hotel.pricePerNight, price: hotel.pricePerNight,
nights: hotel.nights, nights: hotel.nights,
@@ -54,14 +67,13 @@ export default function HotelCard({ hotel }: { hotel: Hotel }) {
<div <div
className="hotel-card cursor-pointer" className="hotel-card cursor-pointer"
onClick={handleCardClick} onClick={handleCardClick}
onMouseEnter={() => dispatchInteraction('product_hover', hotel.id)}
> >
<div className="hotel-image bg-gray-200 flex items-center justify-center"> <div className="hotel-image bg-gray-200 flex items-center justify-center">
<span className="text-gray-400 text-sm">Image</span> <span className="text-gray-400 text-sm">Image</span>
</div> </div>
<div className="hotel-info"> <div className="hotel-info">
<h3 className="hotel-name">{hotel.name}</h3> <h3 ref={titleRef} className="hotel-name">{hotel.name}</h3>
<div className="hotel-location text-sm mb-2">{hotel.roomType}</div> <div className="hotel-location text-sm mb-2">{hotel.roomType}</div>
<div className="text-sm text-[var(--text-secondary)] mb-2"> <div className="text-sm text-[var(--text-secondary)] mb-2">
{hotel.checkIn} - {hotel.checkOut} {hotel.checkIn} - {hotel.checkOut}
@@ -77,7 +89,9 @@ export default function HotelCard({ hotel }: { hotel: Hotel }) {
</div> </div>
<div className="hotel-pricing"> <div className="hotel-pricing">
<div ref={priceRef}>
<PriceDisplay price={hotel.pricePerNight} perNight /> <PriceDisplay price={hotel.pricePerNight} perNight />
</div>
<div className="text-xs text-[var(--text-secondary)] mt-1"> <div className="text-xs text-[var(--text-secondary)] mt-1">
${hotel.pricePerNight * hotel.nights} total for {hotel.nights} night{hotel.nights > 1 ? 's' : ''} ${hotel.pricePerNight * hotel.nights} total for {hotel.nights} night{hotel.nights > 1 ? 's' : ''}
</div> </div>

View File

@@ -42,23 +42,6 @@ export const useInteractionTracking = () => {
setReady(true); setReady(true);
}); });
const handleClick = (e: MouseEvent) => {
if (!sidRef.current) return;
const tgt = e.target as HTMLElement;
const page = window.location.pathname;
track({
sessionId: sidRef.current,
eventName: 'click',
page,
metadata: {
x: e.clientX,
y: e.clientY,
targetEl: tgt.tagName,
targetUrl: tgt instanceof HTMLAnchorElement ? tgt.href : undefined,
},
});
};
const handlePageView = () => { const handlePageView = () => {
if (!sidRef.current) return; if (!sidRef.current) return;
const page = window.location.pathname; const page = window.location.pathname;
@@ -94,11 +77,9 @@ export const useInteractionTracking = () => {
if (!ready) return; if (!ready) return;
handlePageView(); handlePageView();
document.addEventListener('click', handleClick);
document.addEventListener('definedInteraction', handleDefinedInteraction); document.addEventListener('definedInteraction', handleDefinedInteraction);
return () => { return () => {
document.removeEventListener('click', handleClick);
document.removeEventListener('definedInteraction', handleDefinedInteraction); document.removeEventListener('definedInteraction', handleDefinedInteraction);
}; };
}, [ready]); }, [ready]);