From 7336f9126ed4d108457815937562c911742ab90e Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Thu, 12 Mar 2026 21:24:39 +0100 Subject: [PATCH] feat(journeys): add frontend API client and SWR hooks --- lib/api/journeys.ts | 93 ++++++++++++++++++++++++++++++++++++++++++++ lib/swr/dashboard.ts | 53 +++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 lib/api/journeys.ts diff --git a/lib/api/journeys.ts b/lib/api/journeys.ts new file mode 100644 index 0000000..82c2491 --- /dev/null +++ b/lib/api/journeys.ts @@ -0,0 +1,93 @@ +import apiRequest from './client' + +// ─── Types ────────────────────────────────────────────────────────── + +export interface PathTransition { + from_path: string + to_path: string + step_index: number + session_count: number +} + +export interface TransitionsResponse { + transitions: PathTransition[] + total_sessions: number +} + +export interface TopPath { + page_sequence: string[] + session_count: number + avg_duration: number +} + +export interface EntryPoint { + path: string + session_count: number +} + +// ─── Helpers ──────────────────────────────────────────────────────── + +function buildQuery(opts: { + startDate?: string + endDate?: string + depth?: number + limit?: number + min_sessions?: number + entry_path?: string +}): string { + const params = new URLSearchParams() + if (opts.startDate) params.append('start_date', opts.startDate) + if (opts.endDate) params.append('end_date', opts.endDate) + if (opts.depth != null) params.append('depth', opts.depth.toString()) + if (opts.limit != null) params.append('limit', opts.limit.toString()) + if (opts.min_sessions != null) params.append('min_sessions', opts.min_sessions.toString()) + if (opts.entry_path) params.append('entry_path', opts.entry_path) + const query = params.toString() + return query ? `?${query}` : '' +} + +// ─── API Functions ────────────────────────────────────────────────── + +export function getJourneyTransitions( + siteId: string, + startDate?: string, + endDate?: string, + opts?: { depth?: number; minSessions?: number; entryPath?: string } +): Promise { + return apiRequest( + `/sites/${siteId}/journeys/transitions${buildQuery({ + startDate, + endDate, + depth: opts?.depth, + min_sessions: opts?.minSessions, + entry_path: opts?.entryPath, + })}` + ).then(r => r ?? { transitions: [], total_sessions: 0 }) +} + +export function getJourneyTopPaths( + siteId: string, + startDate?: string, + endDate?: string, + opts?: { limit?: number; minSessions?: number; entryPath?: string } +): Promise { + return apiRequest<{ paths: TopPath[] }>( + `/sites/${siteId}/journeys/top-paths${buildQuery({ + startDate, + endDate, + limit: opts?.limit, + min_sessions: opts?.minSessions, + entry_path: opts?.entryPath, + })}` + ).then(r => r?.paths ?? []) +} + +export function getJourneyEntryPoints( + siteId: string, + startDate?: string, + endDate?: string +): Promise { + return apiRequest<{ entry_points: EntryPoint[] }>( + `/sites/${siteId}/journeys/entry-points${buildQuery({ startDate, endDate })}` + ).then(r => r?.entry_points ?? []) +} diff --git a/lib/swr/dashboard.ts b/lib/swr/dashboard.ts index 724c868..c43faa2 100644 --- a/lib/swr/dashboard.ts +++ b/lib/swr/dashboard.ts @@ -17,6 +17,14 @@ import { getDailyStats, getBehavior, } from '@/lib/api/stats' +import { + getJourneyTransitions, + getJourneyTopPaths, + getJourneyEntryPoints, + type TransitionsResponse, + type TopPath as JourneyTopPath, + type EntryPoint, +} from '@/lib/api/journeys' import { listAnnotations } from '@/lib/api/annotations' import type { Annotation } from '@/lib/api/annotations' import { getSite } from '@/lib/api/sites' @@ -55,6 +63,12 @@ const fetchers = { getCampaigns(siteId, start, end, limit), annotations: (siteId: string, start: string, end: string) => listAnnotations(siteId, start, end), behavior: (siteId: string, start: string, end: string) => getBehavior(siteId, start, end), + journeyTransitions: (siteId: string, start: string, end: string, depth?: number, minSessions?: number, entryPath?: string) => + getJourneyTransitions(siteId, start, end, { depth, minSessions, entryPath }), + journeyTopPaths: (siteId: string, start: string, end: string, limit?: number, minSessions?: number, entryPath?: string) => + getJourneyTopPaths(siteId, start, end, { limit, minSessions, entryPath }), + journeyEntryPoints: (siteId: string, start: string, end: string) => + getJourneyEntryPoints(siteId, start, end), } // * Standard SWR config for dashboard data @@ -281,5 +295,44 @@ export function useBehavior(siteId: string, start: string, end: string) { ) } +// * Hook for journey flow transitions (Sankey diagram data) +export function useJourneyTransitions(siteId: string, start: string, end: string, depth?: number, minSessions?: number, entryPath?: string) { + return useSWR( + siteId && start && end ? ['journeyTransitions', siteId, start, end, depth, minSessions, entryPath] : null, + () => fetchers.journeyTransitions(siteId, start, end, depth, minSessions, entryPath), + { + ...dashboardSWRConfig, + refreshInterval: 60 * 1000, + dedupingInterval: 10 * 1000, + } + ) +} + +// * Hook for top journey paths +export function useJourneyTopPaths(siteId: string, start: string, end: string, limit?: number, minSessions?: number, entryPath?: string) { + return useSWR( + siteId && start && end ? ['journeyTopPaths', siteId, start, end, limit, minSessions, entryPath] : null, + () => fetchers.journeyTopPaths(siteId, start, end, limit, minSessions, entryPath), + { + ...dashboardSWRConfig, + refreshInterval: 60 * 1000, + dedupingInterval: 10 * 1000, + } + ) +} + +// * Hook for journey entry points (refreshes less frequently) +export function useJourneyEntryPoints(siteId: string, start: string, end: string) { + return useSWR( + siteId && start && end ? ['journeyEntryPoints', siteId, start, end] : null, + () => fetchers.journeyEntryPoints(siteId, start, end), + { + ...dashboardSWRConfig, + refreshInterval: 5 * 60 * 1000, + dedupingInterval: 30 * 1000, + } + ) +} + // * Re-export for convenience export { fetchers }