// ============================================================================ // App shell — auth gate, top nav, route switch, mount // ============================================================================ function NavItem({ id, active, icon, label, onClick }) { return ( ); } function Shell({ session, onLogout }) { // route: { name, params } const [route, setRoute] = useState({ name: 'home' }); const [assignMeta, setAssignMeta] = useState(null); // Which library folder is open on the home screen. Lifted here (not inside // StudentHome) so that opening a problem and hitting Back returns to the same // folder instead of resetting to the top-level library. const [folderId, setFolderId] = useState(null); const toast = useToast(); // Fetch the problem's meta (title + starter) BEFORE navigating, so Solve mounts // with its starter ready to seed the editor. const openSolve = async (id) => { let meta = null; try { const r = await api.assignments(); meta = r.data.assignments.find((a) => a.id === id) || null; } catch (e) { /* fall through with null meta */ } setAssignMeta(meta); setRoute({ name: 'solve', id }); }; const go = (name) => setRoute({ name }); const isAdmin = session.role === 'admin'; const logout = async () => { await api.logout(); onLogout(); }; let view; if (route.name === 'home') view = ; else if (route.name === 'solve') view = go('home')} />; else if (route.name === 'results') view = ; else if (route.name === 'users') view = ; else if (route.name === 'gradebook') view = ; return (
SC
Stellan's Classroom console
{session.username} {session.username[0].toUpperCase()}
{view}
); } function App() { const [session, setSession] = useState(null); const [booting, setBooting] = useState(true); // Restore the session on load: the cookie is HttpOnly, so /me is the only way // to recover who we are (and whether to show the admin nav) after a refresh. useEffect(() => { api.me() .then((r) => setSession({ username: r.data.username, role: r.data.role })) .catch(() => setSession(null)) // 401 / offline → show Login .finally(() => setBooting(false)); }, []); if (booting) { return
Loading
; } return ( {session ? setSession(null)} /> : } ); } ReactDOM.createRoot(document.getElementById('root')).render(); // Once an entrance animation finishes, drop the .fade-up class so the settled // DOM carries no re-runnable animation (keeps screenshot/PDF capture correct). document.addEventListener('animationend', (e) => { if (e.target && e.target.classList && e.target.classList.contains('fade-up')) { e.target.classList.remove('fade-up'); } }, true);