import { useCallback, useEffect, useRef, useState } from 'react'; import { useClient } from 'hooks/useClient'; import { useFofosChanged } from './channel'; /** * Is this plugin ready to be USED — that is, may anything but Administration be opened? * * ── One condition, read in two places ── * * The nav decides whether a section is clickable and the detail panel decides what to render. Those * are two answers to the same question, and a nav that lets you click a row the panel then refuses is * worse than one that does not offer it. So the condition lives here and both panels read it, rather * than each deciding for itself — the panels are siblings and neither may own the other. * * ── It is a GATE, not a hint ── * * Not ready means the site sections cannot be reached at all, by clicking or by typing `?section=` in * the address bar. Every one of them is a window onto fofos, and without a working connection there is * nothing behind any of them; offering nine doors onto the same "not connected" message taught nobody * anything and made a setup step look like nine faults. * * ── Hardcoded false, deliberately, for now ── * * The shape is real and the plumbing is real: it asks `/status`, it re-asks whenever the connection * changes, and it reports while it is still asking. Only the final verdict is pinned, so turning the * gate on is one line rather than a feature. * * ── Both panels call this, so `/status` is fetched twice ── * * Known and accepted for now: two cheap GETs that answer the same question, kept in step because both * re-run on the same `useFofosChanged` signal. The alternative is a context provider around the two * panels, which is a shared-state boundary this does not yet need — and the duplication is visible * here rather than hidden in a wrapper. If the condition grows expensive, that is the moment to fix * it, not before. * * What it will become is roughly `configured && lastProbeOk` — the connection is stored AND the last * probe reached fofos — but "all the info needed in Administration" is not settled yet and will grow * past those two. That is why this returns a single boolean and not the parts: callers must not start * assembling their own version of the condition out of the pieces. */ type Status = { configured: boolean; lastProbeOk: boolean | null; rootSet: boolean }; export type Readiness = { /** The fofos connection works. Gates every section that READS the site. */ ready: boolean; /** * A root location has been chosen. * * Separate from `ready` because the two are genuinely different questions and one section depends on * each. Media needs a folder on this machine; it never speaks to fofos, and gating it behind a * connection it does not use was the bug that made this distinction necessary. */ rootSet: boolean; /** Still asking. Callers should render nothing rather than guess — see below. */ loading: boolean; }; export const useReady = (): Readiness => { const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const [status, setStatus] = useState(null); const [signal] = useFofosChanged(); const check = useCallback(async () => { try { setStatus(await clientRef.current.get('/fofos-admin/status')); } catch { /* Cannot tell. Treated as CONFIGURED rather than not — a transient failure must never present as "you have not set this up", which is a far more alarming thing to be told and invites somebody to re-enter a connection that was never the problem. */ setStatus({ configured: true, lastProbeOk: null, rootSet: false }); } }, []); useEffect(() => { void check(); }, [check, signal]); // ──────────────────────────────────────────────────────────────────────────────────────────────── // The gate. Replace `false` with the real condition — nothing else here has to change. // ──────────────────────────────────────────────────────────────────────────────────────────────── const ready = false; return { ready, rootSet: status?.rootSet ?? false, loading: status === null }; };