// 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 (
Connecting…
); } if (stage === "login") { return ( <> ); } if (stage === "onboarding") { return ( <> setStage("app")} /> ); } // ── Main app ── const crumb = openProject ? ["Workspace", openProject.client_name] : view === "workspace" ? ["Workspace"] : view === "research" ? ["Research Hub"] : view === "thought" ? ["Thought Leadership"] : view === "leaderboard" ? ["Leaderboard"] : ["Update Profile"]; return (
nav("profile")} onLogout={handleLogout} tier={tier} collapsed={sidebarCollapsed} onToggleCollapse={() => setSidebarCollapsed(c => !c)} currentUser={currentUser} />
setShowCmd(true)} currentUser={currentUser} /> {view === "workspace" && !openProject && ( { setOpenProject(p); setProjectMem(mem); }} onCreate={() => setShowCreate(true)} onNav={nav} currentUser={currentUser} tier={tier} showRbac={tweaks.showRbac} refreshKey={refreshWorkspace} /> )} {view === "workspace" && openProject && ( setOpenProject(null)} onUpload={(folderId) => { setUploadFolder(folderId || null); setShowUpload(true); }} currentUser={currentUser} tier={tier} membership={projectMem} showRbac={tweaks.showRbac} /> )} {view === "research" && } {view === "thought" && } {view === "leaderboard" && } {view === "profile" && ( { setCurrentUser(updated); setToast("Profile saved"); }} /> )}
setShowCreate(false)} tier={tier} onCreated={() => { setToast("Project created · folders ready"); setRefreshWorkspace(k => k + 1); }} /> setShowUpload(false)} onSaved={(name) => setToast(`${name} ingested · metadata saved`)} defaultFolderId={uploadFolder} openProject={openProject} /> setShowCmd(false)} onNav={nav} onCreate={() => setShowCreate(true)} onUpload={() => setShowUpload(true)} /> setToast(null)} /> {/* Hint bar */}
⌘KSearch CNew project UUpload EscClose
); }; // ─── Tweaks panel (theme + appearance only; auth state is now real) ─── const TweaksControls = ({ tweaks, setTweaks }) => ( setTweaks("theme", v)} /> setTweaks("accentHue", v)} /> setTweaks("showRbac", v)} /> ); // Mount const root = ReactDOM.createRoot(document.getElementById("root")); root.render();