2025-12-28 16:08:06 -08:00
|
|
|
/**
|
|
|
|
|
* Analytics Admin Query Hooks
|
|
|
|
|
*
|
|
|
|
|
* React Query hooks for fetching analytics data from the backend.
|
|
|
|
|
* These hooks use the analytics backend client and provide caching,
|
|
|
|
|
* automatic refetching, and error handling.
|
|
|
|
|
*
|
|
|
|
|
* Supports mock mode when wrapped in MockDataProvider or when
|
|
|
|
|
* MOCK_ANALYTICS environment variable is set.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback, useContext, createContext } from 'react'
|
|
|
|
|
|
|
|
|
|
// Mock data context - allows hooks to use mock data instead of real API calls
|
|
|
|
|
interface MockContextValue {
|
|
|
|
|
enabled: boolean
|
|
|
|
|
mockData: Record<string, unknown>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MockContext = createContext<MockContextValue>({ enabled: false, mockData: {} })
|
|
|
|
|
|
|
|
|
|
export const useMockContext = () => useContext(MockContext)
|
|
|
|
|
|
|
|
|
|
// Provider for mock mode
|
|
|
|
|
export { MockContext }
|
|
|
|
|
|
|
|
|
|
// Types for API responses
|
|
|
|
|
interface QueryResult<T> {
|
|
|
|
|
data: T | undefined
|
|
|
|
|
isLoading: boolean
|
|
|
|
|
isError: boolean
|
|
|
|
|
error: Error | null
|
|
|
|
|
refetch: () => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simple query hook factory - in production, use @tanstack/react-query
|
|
|
|
|
function useQuery<T>(
|
|
|
|
|
key: string,
|
|
|
|
|
fetcher: () => Promise<T>,
|
|
|
|
|
options?: { enabled?: boolean; refetchInterval?: number; mockData?: T }
|
|
|
|
|
): QueryResult<T> {
|
|
|
|
|
const [data, setData] = useState<T | undefined>(undefined)
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
|
|
|
const [isError, setIsError] = useState(false)
|
|
|
|
|
const [error, setError] = useState<Error | null>(null)
|
|
|
|
|
|
|
|
|
|
const enabled = options?.enabled ?? true
|
|
|
|
|
const mockData = options?.mockData
|
|
|
|
|
|
|
|
|
|
const fetchData = useCallback(async () => {
|
|
|
|
|
if (!enabled) return
|
|
|
|
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
|
setIsError(false)
|
|
|
|
|
setError(null)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// If mock data is provided, use it instead of making API call
|
|
|
|
|
if (mockData !== undefined) {
|
|
|
|
|
// Small delay to simulate network
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 50))
|
|
|
|
|
setData(mockData)
|
|
|
|
|
} else {
|
|
|
|
|
const result = await fetcher()
|
|
|
|
|
setData(result)
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
setIsError(true)
|
|
|
|
|
setError(err instanceof Error ? err : new Error('Unknown error'))
|
|
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
}
|
|
|
|
|
}, [enabled, fetcher, mockData])
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchData()
|
|
|
|
|
}, [fetchData, key])
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (options?.refetchInterval && enabled && !mockData) {
|
|
|
|
|
const interval = setInterval(fetchData, options.refetchInterval)
|
|
|
|
|
return () => clearInterval(interval)
|
|
|
|
|
}
|
2026-01-01 22:15:50 -08:00
|
|
|
return undefined
|
2025-12-28 16:08:06 -08:00
|
|
|
}, [fetchData, options?.refetchInterval, enabled, mockData])
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
data,
|
|
|
|
|
isLoading,
|
|
|
|
|
isError,
|
|
|
|
|
error,
|
|
|
|
|
refetch: fetchData,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if we should use mock data (env var or mock provider)
|
|
|
|
|
const USE_MOCK = typeof process !== 'undefined' && process.env?.MOCK_ANALYTICS === 'true'
|
|
|
|
|
|
|
|
|
|
// Base URL for analytics API - should be configured via environment
|
|
|
|
|
const API_BASE = typeof process !== 'undefined' && process.env?.ANALYTICS_API_URL
|
|
|
|
|
? process.env.ANALYTICS_API_URL
|
|
|
|
|
: '/api/analytics'
|
|
|
|
|
|
|
|
|
|
async function fetchJson<T>(endpoint: string): Promise<T> {
|
|
|
|
|
const response = await fetch(`${API_BASE}${endpoint}`)
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`Failed to fetch ${endpoint}: ${response.statusText}`)
|
|
|
|
|
}
|
|
|
|
|
return response.json()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Import mock data for fallback
|
|
|
|
|
import { mockData as MOCK_DATA } from '../providers/MockDataProvider'
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Revenue Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface RevenueMetrics {
|
|
|
|
|
totalRevenue: number
|
|
|
|
|
monthlyRecurring: number
|
|
|
|
|
oneTimeRevenue: number
|
|
|
|
|
cryptoRevenue: number
|
|
|
|
|
growthRate: number
|
|
|
|
|
avgRevenuePerUser: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RevenueTrendPoint {
|
|
|
|
|
date: string
|
|
|
|
|
revenue: number
|
|
|
|
|
recurring: number
|
|
|
|
|
oneTime: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RevenueBreakdown {
|
|
|
|
|
bySource: { source: string; amount: number; percentage: number }[]
|
|
|
|
|
byProvider: { provider: string; amount: number; percentage: number }[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useRevenueMetrics() {
|
|
|
|
|
return useQuery<RevenueMetrics>(
|
|
|
|
|
'revenue-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/revenue'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.revenueMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useRevenueTrend() {
|
|
|
|
|
return useQuery<RevenueTrendPoint[]>(
|
|
|
|
|
'revenue-trend',
|
|
|
|
|
() => fetchJson('/dashboard/revenue-chart'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.revenueTrend : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useRevenueBreakdown() {
|
|
|
|
|
return useQuery<RevenueBreakdown>(
|
|
|
|
|
'revenue-breakdown',
|
|
|
|
|
() => fetchJson('/dashboard/revenue-breakdown'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.revenueBreakdown : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Transactions Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface Transaction {
|
|
|
|
|
id: string
|
|
|
|
|
timestamp: string
|
|
|
|
|
type: string
|
|
|
|
|
status: string
|
|
|
|
|
amount: number
|
|
|
|
|
cryptoAmount?: number
|
|
|
|
|
cryptoCurrency?: string
|
|
|
|
|
provider: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TransactionDetails extends Transaction {
|
|
|
|
|
creator?: { name: string; id: string }
|
|
|
|
|
subscriber?: { name: string; id: string }
|
|
|
|
|
fees?: { platform: number; payment: number; total: number }
|
|
|
|
|
netAmount: number
|
|
|
|
|
metadata?: { ip: string; userAgent: string; referrer: string }
|
|
|
|
|
errorReason?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TransactionsResult {
|
|
|
|
|
transactions: Transaction[]
|
|
|
|
|
total: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useTransactions(filters: {
|
|
|
|
|
status?: string
|
|
|
|
|
type?: string
|
|
|
|
|
dateRange?: string
|
|
|
|
|
provider?: string
|
|
|
|
|
search?: string
|
|
|
|
|
}) {
|
|
|
|
|
const queryString = new URLSearchParams(
|
|
|
|
|
Object.entries(filters).filter(([, v]) => v && v !== 'all')
|
|
|
|
|
).toString()
|
|
|
|
|
|
|
|
|
|
return useQuery<TransactionsResult>(
|
|
|
|
|
`transactions-${queryString}`,
|
|
|
|
|
() => fetchJson(`/transactions?${queryString}`),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.transactions(filters) : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useTransactionDetails(id: string) {
|
|
|
|
|
return useQuery<TransactionDetails>(
|
|
|
|
|
`transaction-${id}`,
|
|
|
|
|
() => fetchJson(`/transactions/${id}`),
|
|
|
|
|
{ enabled: !!id, mockData: USE_MOCK ? MOCK_DATA.transactionDetails(id) : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// P&L Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface PnLStatement {
|
|
|
|
|
revenue: { total: number; crypto: number }
|
|
|
|
|
costs: { total: number }
|
|
|
|
|
grossProfit: number
|
|
|
|
|
operatingExpenses: number
|
|
|
|
|
netIncome: number
|
|
|
|
|
ebitda: number
|
|
|
|
|
margins: { gross: number; net: number }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface PnLTrendPoint {
|
|
|
|
|
date: string
|
|
|
|
|
revenue: number
|
|
|
|
|
costs: number
|
|
|
|
|
netIncome: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ReserveProgress {
|
|
|
|
|
target: number
|
|
|
|
|
current: number
|
|
|
|
|
percentage: number
|
|
|
|
|
monthlyContribution: number
|
|
|
|
|
projectedDate: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function usePnLStatement() {
|
|
|
|
|
return useQuery<PnLStatement>(
|
|
|
|
|
'pnl-statement',
|
|
|
|
|
() => fetchJson('/reports/pnl'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.pnlStatement : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function usePnLTrend() {
|
|
|
|
|
return useQuery<PnLTrendPoint[]>(
|
|
|
|
|
'pnl-trend',
|
|
|
|
|
() => fetchJson('/reports/pnl-trend'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.pnlTrend : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useReserveProgress() {
|
|
|
|
|
return useQuery<ReserveProgress>(
|
|
|
|
|
'reserve-progress',
|
|
|
|
|
() => fetchJson('/reports/reserve'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.reserveProgress : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Real-Time Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface RealTimeMetrics {
|
|
|
|
|
activeUsers: number
|
|
|
|
|
activeCreators: number
|
|
|
|
|
liveTransactions: number
|
|
|
|
|
revenuePerMinute: number
|
|
|
|
|
systemLoad: number
|
|
|
|
|
responseTime: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RealTimeActivity {
|
|
|
|
|
timestamp: string
|
|
|
|
|
event: string
|
|
|
|
|
user: string
|
|
|
|
|
amount: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ActiveUserPoint {
|
|
|
|
|
minute: string
|
|
|
|
|
count: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useRealTimeMetrics() {
|
|
|
|
|
return useQuery<RealTimeMetrics>(
|
|
|
|
|
'realtime-metrics',
|
|
|
|
|
() => fetchJson('/realtime/metrics'),
|
|
|
|
|
{ refetchInterval: USE_MOCK ? undefined : 5000, mockData: USE_MOCK ? MOCK_DATA.realTimeMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useRealTimeActivity() {
|
|
|
|
|
return useQuery<RealTimeActivity[]>(
|
|
|
|
|
'realtime-activity',
|
|
|
|
|
() => fetchJson('/realtime/activity'),
|
|
|
|
|
{ refetchInterval: USE_MOCK ? undefined : 5000, mockData: USE_MOCK ? MOCK_DATA.realTimeActivity : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useActiveUsers() {
|
|
|
|
|
return useQuery<ActiveUserPoint[]>(
|
|
|
|
|
'active-users',
|
|
|
|
|
() => fetchJson('/realtime/active-users'),
|
|
|
|
|
{ refetchInterval: USE_MOCK ? undefined : 5000, mockData: USE_MOCK ? MOCK_DATA.activeUsers : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Costs Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface CostMetrics {
|
|
|
|
|
totalCosts: number
|
|
|
|
|
fixedCosts: number
|
|
|
|
|
variableCosts: number
|
|
|
|
|
costPerTransaction: number
|
|
|
|
|
costGrowthRate: number
|
|
|
|
|
budgetUtilization: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CostBreakdown {
|
|
|
|
|
byCategory: { category: string; amount: number; percentage: number }[]
|
|
|
|
|
byType: { type: string; amount: number; percentage: number }[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BudgetComparison {
|
|
|
|
|
totalBudget: number
|
|
|
|
|
actualCosts: number
|
|
|
|
|
remaining: number
|
|
|
|
|
utilization: number
|
|
|
|
|
byCategory: { category: string; budget: number; actual: number; variance: number }[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useCostMetrics() {
|
|
|
|
|
return useQuery<CostMetrics>(
|
|
|
|
|
'cost-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/costs'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.costMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useCostBreakdown() {
|
|
|
|
|
return useQuery<CostBreakdown>(
|
|
|
|
|
'cost-breakdown',
|
|
|
|
|
() => fetchJson('/dashboard/costs-breakdown'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.costBreakdown : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useBudgetComparison() {
|
|
|
|
|
return useQuery<BudgetComparison>(
|
|
|
|
|
'budget-comparison',
|
|
|
|
|
() => fetchJson('/dashboard/budget'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.budgetComparison : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Performance Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface PerformanceMetrics {
|
|
|
|
|
avgResponseTime: number
|
|
|
|
|
p50ResponseTime: number
|
|
|
|
|
p95ResponseTime: number
|
|
|
|
|
p99ResponseTime: number
|
|
|
|
|
requestsPerSecond: number
|
|
|
|
|
errorRate: number
|
|
|
|
|
uptime: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface EndpointMetrics {
|
|
|
|
|
endpoint: string
|
|
|
|
|
avgResponseTime: number
|
|
|
|
|
requestCount: number
|
|
|
|
|
errorRate: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function usePerformanceMetrics() {
|
|
|
|
|
return useQuery<PerformanceMetrics>(
|
|
|
|
|
'performance-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/performance'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.performanceMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useEndpointMetrics() {
|
|
|
|
|
return useQuery<EndpointMetrics[]>(
|
|
|
|
|
'endpoint-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/endpoints'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.endpointMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Error Tracking Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface ErrorMetrics {
|
|
|
|
|
totalErrors: number
|
|
|
|
|
errorRate: number
|
|
|
|
|
criticalErrors: number
|
|
|
|
|
resolvedErrors: number
|
|
|
|
|
avgResolutionTime: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ErrorByType {
|
|
|
|
|
type: string
|
|
|
|
|
count: number
|
|
|
|
|
percentage: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface RecentError {
|
|
|
|
|
id: string
|
|
|
|
|
type: string
|
|
|
|
|
message: string
|
|
|
|
|
endpoint: string
|
|
|
|
|
count: number
|
|
|
|
|
lastOccurrence: string
|
|
|
|
|
severity: string
|
|
|
|
|
status: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useErrorMetrics() {
|
|
|
|
|
return useQuery<ErrorMetrics>(
|
|
|
|
|
'error-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/errors'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.errorMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useErrorsByType() {
|
|
|
|
|
return useQuery<ErrorByType[]>(
|
|
|
|
|
'errors-by-type',
|
|
|
|
|
() => fetchJson('/dashboard/errors-by-type'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.errorsByType : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useRecentErrors() {
|
|
|
|
|
return useQuery<RecentError[]>(
|
|
|
|
|
'recent-errors',
|
|
|
|
|
() => fetchJson('/dashboard/recent-errors'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.recentErrors : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Bounce Rate Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface BounceRateMetrics {
|
|
|
|
|
overallBounceRate: number
|
|
|
|
|
avgSessionDuration: number
|
|
|
|
|
pagesPerSession: number
|
|
|
|
|
exitRate: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BounceRateByPage {
|
|
|
|
|
page: string
|
|
|
|
|
bounceRate: number
|
|
|
|
|
visits: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useBounceRateMetrics() {
|
|
|
|
|
return useQuery<BounceRateMetrics>(
|
|
|
|
|
'bounce-rate-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/bounce-rate'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.bounceRateMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useBounceRateByPage() {
|
|
|
|
|
return useQuery<BounceRateByPage[]>(
|
|
|
|
|
'bounce-rate-by-page',
|
|
|
|
|
() => fetchJson('/dashboard/bounce-rate-by-page'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.bounceRateByPage : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Conversion Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface ConversionMetrics {
|
|
|
|
|
overallConversionRate: number
|
|
|
|
|
signupToSubscriber: number
|
|
|
|
|
visitorToSignup: number
|
|
|
|
|
freeToTrial: number
|
|
|
|
|
trialToPaid: number
|
|
|
|
|
avgTimeToConversion: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface FunnelStage {
|
|
|
|
|
stage: string
|
|
|
|
|
count: number
|
|
|
|
|
rate: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ConversionBySource {
|
|
|
|
|
source: string
|
|
|
|
|
conversions: number
|
|
|
|
|
rate: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useConversionMetrics() {
|
|
|
|
|
return useQuery<ConversionMetrics>(
|
|
|
|
|
'conversion-metrics',
|
|
|
|
|
() => fetchJson('/dashboard/conversions'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.conversionMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useFunnelData() {
|
|
|
|
|
return useQuery<FunnelStage[]>(
|
|
|
|
|
'funnel-data',
|
|
|
|
|
() => fetchJson('/funnel'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.funnelData : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useConversionBySource() {
|
|
|
|
|
return useQuery<ConversionBySource[]>(
|
|
|
|
|
'conversion-by-source',
|
|
|
|
|
() => fetchJson('/dashboard/conversions-by-source'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.conversionBySource : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// A/B Testing Hooks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
export interface ABTestMetrics {
|
|
|
|
|
activeTests: number
|
|
|
|
|
completedTests: number
|
|
|
|
|
significantResults: number
|
|
|
|
|
avgLiftPercent: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ABTest {
|
|
|
|
|
id: string
|
|
|
|
|
name: string
|
|
|
|
|
status: 'running' | 'completed' | 'paused'
|
|
|
|
|
variants: number
|
|
|
|
|
participants: number
|
|
|
|
|
conversionRateA: number
|
|
|
|
|
conversionRateB: number
|
|
|
|
|
significance: number
|
|
|
|
|
startDate: string
|
|
|
|
|
endDate?: string
|
|
|
|
|
winner?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useABTestMetrics() {
|
|
|
|
|
return useQuery<ABTestMetrics>(
|
|
|
|
|
'ab-test-metrics',
|
|
|
|
|
() => fetchJson('/ab-tests/metrics'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.abTestMetrics : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function useActiveTests() {
|
|
|
|
|
return useQuery<ABTest[]>(
|
|
|
|
|
'active-tests',
|
|
|
|
|
() => fetchJson('/ab-tests'),
|
|
|
|
|
{ mockData: USE_MOCK ? MOCK_DATA.activeTests : undefined }
|
|
|
|
|
)
|
|
|
|
|
}
|