// Top-level app — auth gate, stage machine, global state const { useState, useEffect, useCallback } = React; const App = () => { const [tweaks, setTweaks] = useTweaks(window.PRIMA_DEFAULTS); // stage: loading | login | onboarding | app const [stage, setStage] = useState("loading"); const [currentUser, setCurrentUser] = useState(null); const [view, setView] = useState("workspace"); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [openProject, setOpenProject] = useState(null); const [projectMem, setProjectMem] = useState(null); const [showCreate, setShowCreate] = useState(false); const [showUpload, setShowUpload] = useState(false); const [uploadFolder, setUploadFolder] = useState(null); const [showCmd, setShowCmd] = useState(false); const [toast, setToast] = useState(null); const [refreshWorkspace, setRefreshWorkspace] = useState(0); // Restore session from localStorage or URL token (SSO callback) on mount useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const urlToken = urlParams.get("token"); if (urlToken) { PrimaAPI.setToken(urlToken); window.history.replaceState({}, document.title, window.location.pathname); } const token = PrimaAPI.getToken(); if (!token) { setStage("login"); return; } PrimaAPI.getMe() .then(user => { setCurrentUser(user); setStage(user.is_onboarded ? "app" : "onboarding"); }) .catch(() => { PrimaAPI.clearToken(); setStage("login"); }); }, []); // Apply theme + accent at the html root useEffect(() => { document.documentElement.dataset.theme = tweaks.theme; document.documentElement.style.setProperty("--accent-h", tweaks.accentHue); }, [tweaks.theme, tweaks.accentHue]); // Keyboard shortcuts useEffect(() => { const onKey = (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setShowCmd(true); } else if (e.key === "c" && !showCmd && !showCreate && !showUpload && document.activeElement === document.body) { setShowCreate(true); } else if (e.key === "u" && !showCmd && !showCreate && !showUpload && document.activeElement === document.body) { setShowUpload(true); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [showCmd, showCreate, showUpload]); const nav = useCallback((id, opts = {}) => { setView(id); setOpenProject(null); if (opts.create) setShowCreate(true); if (opts.upload) setShowUpload(true); }, []); const handleLogin = (userData) => { // userData = TokenResponse: { access_token, is_onboarded, user_id, full_name, designation, tier } // Refresh full profile so we have all fields (skills, domains, etc.) PrimaAPI.getMe() .then(user => { setCurrentUser(user); setStage(userData.is_onboarded ? "app" : "onboarding"); }) .catch(() => { setCurrentUser({ full_name: userData.full_name, tier: userData.tier }); setStage("app"); }); }; const handleLogout = useCallback(() => { PrimaAPI.clearToken(); setCurrentUser(null); setStage("login"); }, []); const handleOnboardingComplete = () => { PrimaAPI.getMe() .then(user => { setCurrentUser(user); setStage("app"); setToast("Profile saved · welcome to Prima Curator"); }) .catch(() => setStage("app")); }; const tier = currentUser?.tier || tweaks.tier; // ── Stages ── if (stage === "loading") { return (