diff --git a/web/src/components/feats/airline/AirlineCard.tsx b/web/src/components/feats/airline/AirlineCard.tsx index eff36d0..681bf15 100644 --- a/web/src/components/feats/airline/AirlineCard.tsx +++ b/web/src/components/feats/airline/AirlineCard.tsx @@ -1,73 +1,87 @@ 'use client'; import type { EventName } from '@/lib/events'; +import { useHoverTracking } from '@/hooks/useHoverTracking'; const dispatchInteraction = (eventName: EventName, productId?: string, metadata?: Record) => { - const e = new CustomEvent('definedInteraction', { - detail: { eventName, productId, metadata }, - }); - document.dispatchEvent(e); + const e = new CustomEvent('definedInteraction', { + detail: { eventName, productId, metadata }, + }); + document.dispatchEvent(e); }; type CabinClass = 'economy' | 'premium' | 'business' | 'first'; type FareRule = 'flexible' | 'standard' | 'basic'; interface Flight { - id: string; - departure: { time: string; airport: string }; - arrival: { time: string; airport: string }; - duration: string; - stops: number; - cabinClass: CabinClass; - fareRule: FareRule; - refundable: boolean; - basePrice: number; + id: string; + departure: { time: string; airport: string }; + arrival: { time: string; airport: string }; + duration: string; + stops: number; + cabinClass: CabinClass; + fareRule: FareRule; + refundable: boolean; + basePrice: number; } const PriceDisplay = ({ price }: { price: number }) => ( -
${price}
+
${price}
); export default function AirlineCard({ flight }: { flight: Flight }) { - const handleCardClick = () => { - dispatchInteraction('product_view', flight.id, { - cabinClass: flight.cabinClass, - fareRule: flight.fareRule, - price: flight.basePrice, + const durationRef = useHoverTracking({ + eventName: 'hover_over_title', + productId: flight.id, + metadata: { elementText: flight.duration }, }); - }; - return ( -
dispatchInteraction('product_hover', flight.id)} - > -
-
{flight.departure.time}
-
{flight.departure.airport}
-
+ const priceRef = useHoverTracking({ + eventName: 'hover_over_paragraph', + productId: flight.id, + metadata: { elementText: `$${flight.basePrice}` }, + }); -
-
{flight.duration}
-
- {flight.stops === 0 ? 'Direct' : `${flight.stops} stop${flight.stops > 1 ? 's' : ''}`} + const handleCardClick = () => { + dispatchInteraction('view_item_page', flight.id, { + cabinClass: flight.cabinClass, + fareRule: flight.fareRule, + price: flight.basePrice, + }); + }; + + return ( +
+
+
{flight.departure.time}
+
{flight.departure.airport}
+
+ +
+
{flight.duration}
+
+ {flight.stops === 0 ? 'Direct' : `${flight.stops} stop${flight.stops > 1 ? 's' : ''}`} +
+
+ +
+
{flight.arrival.time}
+
{flight.arrival.airport}
+
+ +
+
{flight.cabinClass}
+
{flight.fareRule}
+ {flight.refundable && ( +
Refundable
+ )} +
+ +
+
-
- -
-
{flight.arrival.time}
-
{flight.arrival.airport}
-
- -
-
{flight.cabinClass}
-
{flight.fareRule}
- {flight.refundable && ( -
Refundable
- )} - -
-
- ); + ); } diff --git a/web/src/components/feats/hotel/HotelCard.tsx b/web/src/components/feats/hotel/HotelCard.tsx index 2e635a8..9a8c7e1 100644 --- a/web/src/components/feats/hotel/HotelCard.tsx +++ b/web/src/components/feats/hotel/HotelCard.tsx @@ -1,87 +1,101 @@ 'use client'; import type { EventName } from '@/lib/events'; +import { useHoverTracking } from '@/hooks/useHoverTracking'; const dispatchInteraction = (eventName: EventName, productId?: string, metadata?: Record) => { - const e = new CustomEvent('definedInteraction', { - detail: { eventName, productId, metadata }, - }); - document.dispatchEvent(e); + const e = new CustomEvent('definedInteraction', { + detail: { eventName, productId, metadata }, + }); + document.dispatchEvent(e); }; interface Hotel { - id: string; - name: string; - roomType: string; - checkIn: string; - checkOut: string; - amenities: string[]; - refundable: boolean; - pricePerNight: number; - nights: number; + id: string; + name: string; + roomType: string; + checkIn: string; + checkOut: string; + amenities: string[]; + refundable: boolean; + pricePerNight: number; + nights: number; } const PriceDisplay = ({ price, perNight }: { price: number; perNight: boolean }) => ( -
-
{perNight ? 'Per night' : 'Total'}
-
${price}
- {perNight &&
/night
} -
+
+
{perNight ? 'Per night' : 'Total'}
+
${price}
+ {perNight &&
/night
} +
); const AmenityIcon = ({ name }: { name: string }) => { - const iconMap: Record = { - wifi: 'Wi-Fi', - pool: 'Pool', - gym: 'Gym', - parking: 'Parking', - breakfast: 'Breakfast', - spa: 'Spa', - }; - return {iconMap[name.toLowerCase()] || name}; + const iconMap: Record = { + wifi: 'Wi-Fi', + pool: 'Pool', + gym: 'Gym', + parking: 'Parking', + breakfast: 'Breakfast', + spa: 'Spa', + }; + return {iconMap[name.toLowerCase()] || name}; }; export default function HotelCard({ hotel }: { hotel: Hotel }) { - const handleCardClick = () => { - dispatchInteraction('product_view', hotel.id, { - roomType: hotel.roomType, - price: hotel.pricePerNight, - nights: hotel.nights, + const titleRef = useHoverTracking({ + eventName: 'hover_over_title', + productId: hotel.id, + metadata: { elementText: hotel.name }, }); - }; - return ( -
dispatchInteraction('product_hover', hotel.id)} - > -
- Image -
+ const priceRef = useHoverTracking({ + eventName: 'hover_over_paragraph', + productId: hotel.id, + metadata: { elementText: `$${hotel.pricePerNight}` }, + }); -
-

{hotel.name}

-
{hotel.roomType}
-
- {hotel.checkIn} - {hotel.checkOut} -
-
- {hotel.amenities.map((a) => ( - - ))} -
- {hotel.refundable && ( -
Free cancellation
- )} -
+ const handleCardClick = () => { + dispatchInteraction('view_item_page', hotel.id, { + roomType: hotel.roomType, + price: hotel.pricePerNight, + nights: hotel.nights, + }); + }; -
- -
- ${hotel.pricePerNight * hotel.nights} total for {hotel.nights} night{hotel.nights > 1 ? 's' : ''} + return ( +
+
+ Image +
+ +
+

{hotel.name}

+
{hotel.roomType}
+
+ {hotel.checkIn} - {hotel.checkOut} +
+
+ {hotel.amenities.map((a) => ( + + ))} +
+ {hotel.refundable && ( +
Free cancellation
+ )} +
+ +
+
+ +
+
+ ${hotel.pricePerNight * hotel.nights} total for {hotel.nights} night{hotel.nights > 1 ? 's' : ''} +
+
-
-
- ); + ); } diff --git a/web/src/hooks/useInteractionTracking.ts b/web/src/hooks/useInteractionTracking.ts index 26173cd..563e9ec 100644 --- a/web/src/hooks/useInteractionTracking.ts +++ b/web/src/hooks/useInteractionTracking.ts @@ -42,23 +42,6 @@ export const useInteractionTracking = () => { 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 = () => { if (!sidRef.current) return; const page = window.location.pathname; @@ -94,11 +77,9 @@ export const useInteractionTracking = () => { if (!ready) return; handlePageView(); - document.addEventListener('click', handleClick); document.addEventListener('definedInteraction', handleDefinedInteraction); return () => { - document.removeEventListener('click', handleClick); document.removeEventListener('definedInteraction', handleDefinedInteraction); }; }, [ready]);