feat(journeys): add frontend API client and SWR hooks
This commit is contained in:
93
lib/api/journeys.ts
Normal file
93
lib/api/journeys.ts
Normal file
@@ -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<TransitionsResponse> {
|
||||||
|
return apiRequest<TransitionsResponse>(
|
||||||
|
`/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<TopPath[]> {
|
||||||
|
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<EntryPoint[]> {
|
||||||
|
return apiRequest<{ entry_points: EntryPoint[] }>(
|
||||||
|
`/sites/${siteId}/journeys/entry-points${buildQuery({ startDate, endDate })}`
|
||||||
|
).then(r => r?.entry_points ?? [])
|
||||||
|
}
|
||||||
@@ -17,6 +17,14 @@ import {
|
|||||||
getDailyStats,
|
getDailyStats,
|
||||||
getBehavior,
|
getBehavior,
|
||||||
} from '@/lib/api/stats'
|
} 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 { listAnnotations } from '@/lib/api/annotations'
|
||||||
import type { Annotation } from '@/lib/api/annotations'
|
import type { Annotation } from '@/lib/api/annotations'
|
||||||
import { getSite } from '@/lib/api/sites'
|
import { getSite } from '@/lib/api/sites'
|
||||||
@@ -55,6 +63,12 @@ const fetchers = {
|
|||||||
getCampaigns(siteId, start, end, limit),
|
getCampaigns(siteId, start, end, limit),
|
||||||
annotations: (siteId: string, start: string, end: string) => listAnnotations(siteId, start, end),
|
annotations: (siteId: string, start: string, end: string) => listAnnotations(siteId, start, end),
|
||||||
behavior: (siteId: string, start: string, end: string) => getBehavior(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
|
// * 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<TransitionsResponse>(
|
||||||
|
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<JourneyTopPath[]>(
|
||||||
|
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<EntryPoint[]>(
|
||||||
|
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
|
// * Re-export for convenience
|
||||||
export { fetchers }
|
export { fetchers }
|
||||||
|
|||||||
Reference in New Issue
Block a user