'use client'; import { useState, useEffect, type ReactNode } from 'react'; import AdminLogin from './components/AdminLogin'; import AdminDashboard from './components/AdminDashboard'; import { SESSION_EXPIRED_EVENT } from './components/sessionExpiry'; import './admin.css'; /** * Auth gate and panel chrome for every /admin route. Lives in the admin layout * rather than in each page, so navigating between tabs keeps the session check * and the header mounted instead of re-running them per route. */ export default function AdminShell({ children }: { children: ReactNode }) { const [authenticated, setAuthenticated] = useState(null); const [enabled, setEnabled] = useState(true); // Sessions last 24 hours (lib/admin/auth.ts), but nothing re-checked // authentication after the mount-time check below — leave a tab open // overnight and every admin request answered 401 while the UI still looked // signed in (#596). SESSION_EXPIRED_EVENT is raised by the fetch call sites // that noticed; this is what turns that into dropping back to the login // screen with an explanation instead of each of them failing quietly. const [sessionExpired, setSessionExpired] = useState(false); useEffect(() => { async function checkAuth() { try { const res = await fetch('/api/admin/auth'); const data = await res.json(); setAuthenticated(data.authenticated); setEnabled(data.enabled); } catch { setAuthenticated(false); } } checkAuth(); }, []); useEffect(() => { function onSessionExpired() { setAuthenticated(false); setSessionExpired(true); } window.addEventListener(SESSION_EXPIRED_EVENT, onSessionExpired); return () => window.removeEventListener(SESSION_EXPIRED_EVENT, onSessionExpired); }, []); if (authenticated === null) { return (
); } if (!enabled) { return (

Admin Panel

The admin panel is not enabled. Set ADMIN_PASSWORD in your environment variables to activate it.

); } if (!authenticated) { return ( { setSessionExpired(false); setAuthenticated(true); }} /> ); } return setAuthenticated(false)}>{children}; }