diff --git a/src/app/(jury)/jury/page.tsx b/src/app/(jury)/jury/page.tsx index ccd2da0..c113d99 100644 --- a/src/app/(jury)/jury/page.tsx +++ b/src/app/(jury)/jury/page.tsx @@ -3,6 +3,7 @@ import { Suspense } from 'react' import Link from 'next/link' import { auth } from '@/lib/auth' import { prisma } from '@/lib/prisma' +import { AutoRefresh } from '@/components/shared/auto-refresh' export const metadata: Metadata = { title: 'Jury Dashboard' } export const dynamic = 'force-dynamic' @@ -744,6 +745,9 @@ export default async function JuryDashboardPage() { }> + + {/* Auto-refresh every 30s so voting round changes appear promptly */} + ) } diff --git a/src/components/shared/auto-refresh.tsx b/src/components/shared/auto-refresh.tsx new file mode 100644 index 0000000..2299df1 --- /dev/null +++ b/src/components/shared/auto-refresh.tsx @@ -0,0 +1,42 @@ +'use client' + +import { useEffect } from 'react' +import { useRouter } from 'next/navigation' + +/** + * Invisible client component that periodically calls router.refresh() + * to re-fetch server component data without a full page reload. + * Pauses when the tab is hidden to avoid unnecessary requests. + */ +export function AutoRefresh({ intervalMs = 30_000 }: { intervalMs?: number }) { + const router = useRouter() + + useEffect(() => { + let timer: ReturnType + + function start() { + timer = setInterval(() => { + router.refresh() + }, intervalMs) + } + + function handleVisibility() { + clearInterval(timer) + if (document.visibilityState === 'visible') { + // Refresh immediately when tab becomes visible again + router.refresh() + start() + } + } + + start() + document.addEventListener('visibilitychange', handleVisibility) + + return () => { + clearInterval(timer) + document.removeEventListener('visibilitychange', handleVisibility) + } + }, [router, intervalMs]) + + return null +}