import { useCallback, useState } from 'react'
import { Loading, ErrorState } from '../../../components/PageState.jsx'
import { useAsync } from '../../../lib/useAsync.js'
import { ago, dateTime } from '../../../lib/format.js'
import { api } from '../../../api/client.js'
import { useSite } from '../../../contexts/SiteContext.jsx'
import { useAuth } from '../../../contexts/AuthContext.jsx'
export default function Dashboard() {
const { refresh: refreshSite } = useSite()
const { user } = useAuth()
// PUT /admin/site-mode is adminOnly. The dashboard itself is staff-wide, so the
// toggle needs its own gate — same rule the sidebar follows (AdminLayout: never
// show a non-admin a control that would 403).
const isAdmin = user?.role === 'admin'
const [tick, setTick] = useState(0)
const reload = useCallback(() => setTick((t) => t + 1), [])
const { loading, error, data } = useAsync(
() => Promise.all([api.admin.dashboard(), api.admin.listPosts(), api.admin.listWiki()]),
[tick],
)
const [busy, setBusy] = useState(false)
const [modeError, setModeError] = useState('')
if (loading) return
if (error) return
const [dash, posts, wiki] = data
const mode = dash.site_mode || 'live'
const isLive = mode === 'live'
const modeDot = isLive ? 'var(--mode-live)' : 'var(--mode-maint)'
const published = posts.filter((p) => p.published).length
const stats = [
{ value: published, label: 'Published posts' },
{ value: posts.length - published, label: 'Drafts' },
{ value: wiki.length, label: 'Wiki pages' },
{ value: dash.counts?.users ?? 0, label: 'Users' },
]
// The rejection was previously unhandled: a refused toggle surfaced only as an
// unhandled promise rejection in the console while the button silently reverted.
async function toggle() {
setBusy(true)
setModeError('')
try {
await api.admin.setSiteMode(isLive ? 'maintenance' : 'live')
await refreshSite()
reload()
} catch (err) {
setModeError(
err.status === 403
? 'Only an administrator can change the site mode.'
: 'Could not change the site mode. Try again.',
)
} finally {
setBusy(false)
}
}
const changed = dash.last_change || {}
let modeLabel = isLive ? 'Switch to Maintenance' : 'Switch to Live'
if (busy) modeLabel = 'Saving…'
return (
{/* Operator warnings: things that are quietly not working and would
otherwise be discovered by someone not receiving an email. The list is
normally empty, which is why it sits above the fold rather than in a
panel — see ENGAGEMENT.md §1.2a (G22). */}
{(dash.warnings || []).map((w) => (