diff --git a/app/share/[id]/page.tsx b/app/share/[id]/page.tsx index 9360a52..74040b7 100644 --- a/app/share/[id]/page.tsx +++ b/app/share/[id]/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { useParams, useSearchParams, useRouter } from 'next/navigation' -import { getPublicDashboard, type DashboardData } from '@/lib/api/stats' +import { getPublicDashboard, getPublicStats, getPublicDailyStats, getPublicRealtime, getPublicPerformanceByPage, type DashboardData, type Stats, type DailyStat, type PerformanceByPageStat } from '@/lib/api/stats' import { toast } from 'sonner' import { LoadingOverlay } from '@ciphera-net/ui' import Chart from '@/components/dashboard/Chart' @@ -48,14 +48,42 @@ export default function PublicDashboardPage() { const [todayInterval, setTodayInterval] = useState<'minute' | 'hour'>('hour') const [multiDayInterval, setMultiDayInterval] = useState<'hour' | 'day'>('day') + // Previous period data + const [prevStats, setPrevStats] = useState(undefined) + const [prevDailyStats, setPrevDailyStats] = useState(undefined) + + const getPreviousDateRange = (start: string, end: string) => { + const startDate = new Date(start) + const endDate = new Date(end) + const duration = endDate.getTime() - startDate.getTime() + + // * If duration is 0 (Today), set previous range to yesterday + if (duration === 0) { + const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000) + const prevStart = prevEnd + return { + start: prevStart.toISOString().split('T')[0], + end: prevEnd.toISOString().split('T')[0] + } + } + + const prevEnd = new Date(startDate.getTime() - 24 * 60 * 60 * 1000) + const prevStart = new Date(prevEnd.getTime() - duration) + + return { + start: prevStart.toISOString().split('T')[0], + end: prevEnd.toISOString().split('T')[0] + } + } + // Auto-refresh interval (for realtime) useEffect(() => { const interval = setInterval(() => { // Only refresh realtime count if we have data if (data && !isPasswordProtected) { - loadDashboard(true) + loadRealtime() } - }, 10000) // 10 seconds + }, 30000) // 30 seconds return () => clearInterval(interval) }, [data, isPasswordProtected, dateRange, password]) @@ -64,25 +92,66 @@ export default function PublicDashboardPage() { loadDashboard() }, [siteId, dateRange, todayInterval, multiDayInterval]) + const loadRealtime = async () => { + try { + const auth = { + password, + captcha: { + captcha_id: captchaId, + captcha_solution: captchaSolution, + captcha_token: captchaToken + } + } + const realtimeData = await getPublicRealtime(siteId, auth) + if (data) { + setData({ + ...data, + realtime_visitors: realtimeData.visitors + }) + } + } catch (error) { + // Silently fail for realtime updates + } + } + const loadDashboard = async (silent = false) => { try { if (!silent) setLoading(true) - const dashboardData = await getPublicDashboard( - siteId, - dateRange.start, - dateRange.end, - 10, - dateRange.start === dateRange.end ? todayInterval : multiDayInterval, + const interval = dateRange.start === dateRange.end ? todayInterval : multiDayInterval + const auth = { password, - { + captcha: { captcha_id: captchaId, captcha_solution: captchaSolution, captcha_token: captchaToken } - ) + } + + const [dashboardData, prevStatsData, prevDailyStatsData] = await Promise.all([ + getPublicDashboard( + siteId, + dateRange.start, + dateRange.end, + 10, + interval, + password, + auth.captcha + ), + (async () => { + const prevRange = getPreviousDateRange(dateRange.start, dateRange.end) + return getPublicStats(siteId, prevRange.start, prevRange.end, auth) + })(), + (async () => { + const prevRange = getPreviousDateRange(dateRange.start, dateRange.end) + return getPublicDailyStats(siteId, prevRange.start, prevRange.end, interval, auth) + })() + ]) setData(dashboardData) + setPrevStats(prevStatsData) + setPrevDailyStats(prevDailyStatsData) + setIsPasswordProtected(false) // Reset captcha setCaptchaId('') @@ -304,8 +373,9 @@ export default function PublicDashboardPage() {
@@ -313,7 +383,23 @@ export default function PublicDashboardPage() { {/* Performance Stats - Only show if enabled */} {performance && data.site?.enable_performance_insights && (
- + { + return getPublicPerformanceByPage(siteId, startDate, endDate, opts, { + password, + captcha: { + captcha_id: captchaId, + captcha_solution: captchaSolution, + captcha_token: captchaToken + } + }) + }} + />
)} diff --git a/lib/api/stats.ts b/lib/api/stats.ts index 68c80da..fd698af 100644 --- a/lib/api/stats.ts +++ b/lib/api/stats.ts @@ -82,6 +82,18 @@ export interface RealtimeStats { visitors: number } +export interface AuthParams { + password?: string + captcha?: { captcha_id?: string, captcha_solution?: string, captcha_token?: string } +} + +function appendAuthParams(params: URLSearchParams, auth?: AuthParams) { + if (auth?.password) params.append('password', auth.password) + if (auth?.captcha?.captcha_id) params.append('captcha_id', auth.captcha.captcha_id) + if (auth?.captcha?.captcha_solution) params.append('captcha_solution', auth.captcha.captcha_solution) + if (auth?.captcha?.captcha_token) params.append('captcha_token', auth.captcha.captcha_token) +} + export async function getStats(siteId: string, startDate?: string, endDate?: string): Promise { const params = new URLSearchParams() if (startDate) params.append('start_date', startDate) @@ -90,10 +102,25 @@ export async function getStats(siteId: string, startDate?: string, endDate?: str return apiRequest(`/sites/${siteId}/stats${query ? `?${query}` : ''}`) } +export async function getPublicStats(siteId: string, startDate?: string, endDate?: string, auth?: AuthParams): Promise { + const params = new URLSearchParams() + if (startDate) params.append('start_date', startDate) + if (endDate) params.append('end_date', endDate) + appendAuthParams(params, auth) + const query = params.toString() + return apiRequest(`/public/sites/${siteId}/stats${query ? `?${query}` : ''}`) +} + export async function getRealtime(siteId: string): Promise { return apiRequest(`/sites/${siteId}/realtime`) } +export async function getPublicRealtime(siteId: string, auth?: AuthParams): Promise { + const params = new URLSearchParams() + appendAuthParams(params, auth) + return apiRequest(`/public/sites/${siteId}/realtime?${params.toString()}`) +} + export async function getTopPages(siteId: string, startDate?: string, endDate?: string, limit = 10): Promise { const params = new URLSearchParams() if (startDate) params.append('start_date', startDate) @@ -166,6 +193,15 @@ export async function getDailyStats(siteId: string, startDate?: string, endDate? return apiRequest<{ stats: DailyStat[] }>(`/sites/${siteId}/daily?${params.toString()}`).then(r => r?.stats || []) } +export async function getPublicDailyStats(siteId: string, startDate?: string, endDate?: string, interval?: string, auth?: AuthParams): Promise { + const params = new URLSearchParams() + if (startDate) params.append('start_date', startDate) + if (endDate) params.append('end_date', endDate) + if (interval) params.append('interval', interval) + appendAuthParams(params, auth) + return apiRequest<{ stats: DailyStat[] }>(`/public/sites/${siteId}/daily?${params.toString()}`).then(r => r?.stats || []) +} + export async function getEntryPages(siteId: string, startDate?: string, endDate?: string, limit = 10): Promise { const params = new URLSearchParams() if (startDate) params.append('start_date', startDate) @@ -207,6 +243,25 @@ export async function getPerformanceByPage( return res?.performance_by_page ?? [] } +export async function getPublicPerformanceByPage( + siteId: string, + startDate?: string, + endDate?: string, + opts?: { limit?: number; sort?: 'lcp' | 'cls' | 'inp' }, + auth?: AuthParams +): Promise { + const params = new URLSearchParams() + if (startDate) params.append('start_date', startDate) + if (endDate) params.append('end_date', endDate) + if (opts?.limit != null) params.append('limit', String(opts.limit)) + if (opts?.sort) params.append('sort', opts.sort) + appendAuthParams(params, auth) + const res = await apiRequest<{ performance_by_page: PerformanceByPageStat[] }>( + `/public/sites/${siteId}/performance-by-page?${params.toString()}` + ) + return res?.performance_by_page ?? [] +} + export interface DashboardData { site: Site stats: Stats @@ -249,10 +304,8 @@ export async function getPublicDashboard( if (startDate) params.append('start_date', startDate) if (endDate) params.append('end_date', endDate) if (interval) params.append('interval', interval) - if (password) params.append('password', password) - if (captcha?.captcha_id) params.append('captcha_id', captcha.captcha_id) - if (captcha?.captcha_solution) params.append('captcha_solution', captcha.captcha_solution) - if (captcha?.captcha_token) params.append('captcha_token', captcha.captcha_token) + + appendAuthParams(params, { password, captcha }) params.append('limit', limit.toString()) return apiRequest(`/public/sites/${siteId}/dashboard?${params.toString()}`)