import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; import { useAuthToken } from "../hooks/use-root-data"; import { getApiBaseUrl } from "../utils/api-config"; /** * Records that the authenticated user viewed a product. Used as a signal for * the recommendation system (alongside bookmarks, follows, orders, semantic * proximity). Fire-and-forget: failures are swallowed so tracking never * disrupts the browsing experience. * * NOTE: this hits /api/engagement/v1/views/ via a direct fetch because the * endpoint is newer than the generated OpenAPI client. Swap to the generated * EngagementApi method once the client is regenerated. */ export function useTrackProductView() { const token = useAuthToken(); const queryClient = useQueryClient(); return useMutation({ mutationFn: async (productId: string) => { if (!token || !productId) return { skipped: true }; const res = await fetch(`${getApiBaseUrl()}/api/engagement/v1/views/`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ productId }), }); if (!res.ok) { throw new Error(`Failed to track view: ${res.status}`); } return res.json(); }, onSuccess: () => { // Keep "recently viewed" fresh if/when it's rendered. queryClient.invalidateQueries({ queryKey: ["recentlyViewedProducts"] }); }, // View tracking is best-effort; never surface errors to the user. onError: () => {}, }); } /** * Fires a single product-view event when a product page is opened. Dedupes per * productId so React strict-mode double-mounts / re-renders don't double-count * within the same mount, and skips when not authenticated. */ export function useRecordProductView( productId: string | undefined, options?: { enabled?: boolean } ) { const token = useAuthToken(); const { mutate } = useTrackProductView(); const lastTrackedRef = useRef(null); const enabled = options?.enabled ?? true; useEffect(() => { if (!enabled) return; if (!token || !productId) return; if (lastTrackedRef.current === productId) return; lastTrackedRef.current = productId; mutate(productId); }, [productId, token, enabled, mutate]); }