import { useCallback, useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { useClient } from 'hooks/useClient'; import { DEFAULT_SECTION, SECTIONS, isSectionKey, type SectionKey } from './sections'; import { HostsPanel } from './HostsPanel'; import { CertificatesPanel } from './CertificatesPanel'; import { ConnectionPanel } from './ConnectionPanel'; import { ListSection, REDIRECTIONS, STREAMS, DEAD_HOSTS, ACCESS_LISTS } from './ListSection'; import { PlannedSection } from './PlannedSection'; import { useNpmChanged } from './channel'; /** * The right column: whichever section the nav has selected. * * It reads `?section=` from the URL rather than being handed it — the two panels are siblings in a * workspace and neither owns the other, and `docs/navigation-audit.md` puts addressable state in the URL * for exactly this reason. A bogus or missing value falls back rather than rendering nothing. * * ── The unconfigured check lives HERE, once ── * * Every section needs a connection, so every section would otherwise have to handle its absence, and each * one would say something slightly different. Asking `/status` once and rendering the connection form * instead of any section is the same answer for all seven, and it means "no connection yet" reads as a * setup step rather than as six broken lists. */ type Status = { configured: boolean }; export const DetailPanel = () => { const [params] = useSearchParams(); const raw = params.get('section'); const section: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION; const client = useClient(); const clientRef = useRef(client); clientRef.current = client; const [configured, setConfigured] = useState(null); const [connectionSignal] = useNpmChanged(); const check = useCallback(async () => { try { const status = await clientRef.current.get('/proxy-manager/status'); setConfigured(status.configured); } catch { // Cannot tell. Render the section rather than the setup form — a transient failure must not look // like "you have not set this up", which is a much more alarming thing to be told. setConfigured(true); } }, []); useEffect(() => { void check(); }, [check, connectionSignal]); if (configured === null) { return
Loading…
; } // A planned section says so before anything else — including before the connection check, because "you // have not connected NPM" is not why Domains is empty and would be a misleading thing to be told. const planned = SECTIONS.find((s) => s.key === section && s.planned); if (planned) return ; if (!configured || section === 'connection') return ; switch (section) { case 'proxy-hosts': return ; case 'certificates': return ; case 'redirections': return ; case 'streams': return ; case 'dead-hosts': return ; case 'access-lists': return ; default: // `domains` and `dns-providers` are handled above; this keeps the switch total without a cast. return null; } };