/** * Loads the history timeline from `GET /api/history`. * * Goes through the shared client, so the request is deduped and cached * for 60s like every other read. Two consequences worth knowing: * * · `reload` has to invalidate before it refetches. Without that, the * retry button inside the TTL would hand back the same settled * promise and look like it did nothing. A *failed* request is * already evicted by the client, so this matters for the refresh * case rather than the error case. * * · there's no AbortController. `get` shares one promise between * callers, so aborting on unmount would cancel someone else's * request. The `live` flag drops the result instead. * * No `fallback` on purpose. Handing this the mock data would render a * plausible-looking history with no indication the server is down, and * the wrong history is worse than a visible error — the same reason * `fallback: EMPTY` came out elsewhere. * * `undated` is the number of published entries the API left out because * nothing gave them a date. Not rendered publicly — a visitor can't act * on it — but returned so it's reachable if you want a warning in the * admin later. */ import { useCallback, useEffect, useState } from 'react' import { get, invalidate, ApiError } from './api.js' import type { TimelineItem } from './timeline' const PATH = '/history' type HistoryResponse = { items?: TimelineItem[] undated?: number } type State = { items: TimelineItem[] undated: number loading: boolean error: string | null reload: () => void } export function useHistory(): State { const [items, setItems] = useState([]) const [undated, setUndated] = useState(0) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [attempt, setAttempt] = useState(0) const reload = useCallback(() => { invalidate(PATH) setAttempt((n) => n + 1) }, []) useEffect(() => { let live = true async function load() { setLoading(true) setError(null) try { const data: HistoryResponse = await get(PATH) if (!live) return setItems(Array.isArray(data?.items) ? data.items : []) setUndated(Number(data?.undated) || 0) } catch (err) { if (!live) return setError( err instanceof ApiError ? err.message : "Couldn't reach the server.", ) } finally { if (live) setLoading(false) } } load() return () => { live = false } }, [attempt]) return { items, undated, loading, error, reload } }