/* ============================================================ app.jsx — LIVE PWA shell. Fetches /api/dashboard (the window.OCC contract computed by the backend), gates behind the Argon2id session, and shows a data-source banner (sample / live / building). The design-time demo-scenario switcher and tweaks panel are removed: the dashboard now reflects real computed values only. Screens (Home/Recovery/Sleep/Trends/Clinical) and primitives are loaded unchanged from the design files. ============================================================ */ (function () { const { useState, useEffect, useCallback } = React; const ACCENT = { good: 132, attn: 352, acute: 18 }; // fixed "Sage / Plum" function api(path, opts = {}) { return fetch(path, Object.assign( { credentials: "same-origin", headers: { "Content-Type": "application/json" } }, opts)); } // ---- device chrome (unchanged from the design) ---------- function StatusBar() { return (
6:42 {[0,1,2,3].map(i=>)}
); } function AppBar({ route, go, theme, toggleTheme, onLogout }) { const detail = route.screen === "recovery" || route.screen === "sleep"; return (
{detail ? ( ) : (
openclawclinic
)}
); } function TabBar({ route, go, alert }) { const tabs = [["home", "Home", "home"], ["trends", "Trends", "trend"], ["clinical", "Clinical", "clinical"]]; const activeTab = ["recovery", "sleep"].includes(route.screen) ? "home" : route.screen; return ( ); } // ---- data-source banner (sample / live / building) ------ function SourceBanner({ ds }) { if (!ds) return null; const tone = ds.mode === "live" ? "var(--good)" : ds.mode === "building" ? "var(--attn)" : "var(--ink-3)"; const label = { live: "LIVE", building: `BASELINE BUILDING · ${ds.nights_valid}/${ds.baseline_target}`, sample: "SAMPLE DATA", none: "NO DATA" }[ds.mode] || ds.mode; return (
{label} {ds.note}
); } // ---- login (styled with tokens) ------------------------- function Login({ onLogin }) { const [u, setU] = useState(""); const [p, setP] = useState(""); const [err, setErr] = useState(null); const [busy, setBusy] = useState(false); const submit = async (e) => { e.preventDefault(); setBusy(true); setErr(null); try { const r = await api("/api/auth/login", { method: "POST", body: JSON.stringify({ username: u, password: p }) }); if (r.ok) { onLogin(); return; } const d = await r.json().catch(() => ({})); setErr(r.status === 429 ? "Too many attempts — locked out briefly." : (d.detail || "Invalid credentials")); } catch (e) { setErr("Network error"); } finally { setBusy(false); } }; return (
openclawclinic
SIGN IN
setU(e.target.value)} placeholder="username" autoComplete="username" style={inp()} /> setP(e.target.value)} placeholder="password" type="password" autoComplete="current-password" style={inp()} /> {err &&
{err}
}
); } const Center = ({ children }) => (
{children}
); function App() { const [theme, setTheme] = useState(() => localStorage.getItem("occ-theme") || "light"); const [authed, setAuthed] = useState(null); // null=checking const [dash, setDash] = useState(null); const [err, setErr] = useState(null); const [route, setRoute] = useState({ screen: "home", param: null }); useEffect(() => { document.documentElement.dataset.theme = theme; localStorage.setItem("occ-theme", theme); }, [theme]); useEffect(() => { const r = document.documentElement.style; r.setProperty("--good-h", ACCENT.good); r.setProperty("--attn-h", ACCENT.attn); r.setProperty("--acute-h", ACCENT.acute); }, []); const loadDash = useCallback(async () => { try { const r = await api("/api/dashboard"); if (r.status === 401) { setAuthed(false); return; } setDash(await r.json()); } catch (e) { setErr(String(e)); } }, []); useEffect(() => { (async () => { try { const r = await api("/api/auth/me"); if (r.ok) { setAuthed(true); loadDash(); } else setAuthed(false); } catch (e) { setAuthed(false); } })(); }, [loadDash]); const go = (screen, param = null) => { setRoute({ screen, param }); document.querySelector(".screen")?.scrollTo(0, 0); }; const logout = async () => { await api("/api/auth/logout", { method: "POST" }); setAuthed(false); setDash(null); setRoute({ screen: "home", param: null }); }; const toggleTheme = () => setTheme((x) => x === "dark" ? "light" : "dark"); const frame = (children) => (
{children}
); if (authed === null) return frame(<>
Loading…
); if (authed === false) return frame(<> { setAuthed(true); loadDash(); }} />); if (err) return frame(<>
Couldn’t load data.
{err}
); if (!dash) return frame(<>
Loading your data…
); if (!dash.available) { return frame(<>
No data yet
{dash.data_source?.note}
); } // expose the live contract for screens that read globals window.OCC = { trends: dash.trends, clinicalFor: () => dash.clinical }; const data = dash.scenario; const alert = (data.acute || []).length ? "acute" : (data.subacute || []).length ? "attn" : null; let body; if (route.screen === "home") body = ; else if (route.screen === "recovery") body = ; else if (route.screen === "sleep") body = ; else if (route.screen === "trends") body = ; else if (route.screen === "clinical") body = ; return (
{body}
); } // small style helpers function btn() { return { display: "flex", alignItems: "center", gap: 7, background: "none", border: "none", color: "var(--ink)", padding: 0 }; } function iconBtn() { return { width: 34, height: 34, borderRadius: 999, border: "1px solid var(--line)", background: "var(--surface)", display: "grid", placeItems: "center" }; } function inp() { return { padding: "11px 13px", borderRadius: "var(--r-sm, 10px)", border: "1px solid var(--line)", background: "var(--surface)", color: "var(--ink)", fontSize: 14, fontFamily: "var(--font-ui)" }; } window.OCCApp = App; })();