import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ThemeProvider } from './contexts/ThemeContext'; import { AppConfigProvider } from './contexts/AppConfigContext'; import HomePage from './pages/HomePage'; import ChatPage from './pages/ChatPage'; import AuthPage from './pages/AuthPage'; import CanvasPage from './pages/CanvasPage'; import JourneyPage from './pages/JourneyPage'; import UserGuide from './components/UserGuide'; import './styles/components.css'; import { chatActiveState, chatStarterState, homeState, mapPopState, sameHistoryState, } from './utils/chatHistoryNav'; let didSeedRestoredChatHistory = false; function App() { const [currentView, setCurrentView] = useState('home'); const [chatNavView, setChatNavView] = useState(null); const [chatStarterNonce, setChatStarterNonce] = useState(0); const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState(null); const [authToken, setAuthToken] = useState(null); const historyStateRef = useRef(null); const chatNavViewRef = useRef(null); chatNavViewRef.current = chatNavView; const applyHistoryState = useCallback((state, { fromPop = false, replace = false } = {}) => { const mapped = mapPopState(state); if (!fromPop) { if (sameHistoryState(historyStateRef.current, state)) { setCurrentView(mapped.currentView); setChatNavView(mapped.chatNavView); return; } if (replace) { window.history.replaceState(state, ''); } else { window.history.pushState(state, ''); } } historyStateRef.current = state || null; setCurrentView(mapped.currentView); if (mapped.chatNavView === 'starter' && chatNavViewRef.current === 'active') { setChatStarterNonce((n) => n + 1); } setChatNavView(mapped.chatNavView); }, []); useEffect(() => { const onPop = (event) => { applyHistoryState(event.state, { fromPop: true }); }; window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); }, [applyHistoryState]); useEffect(() => { let cancelled = false; const token = localStorage.getItem('authToken'); const userData = localStorage.getItem('user'); if (!token || !userData) return undefined; let parsedUser; try { parsedUser = JSON.parse(userData); } catch { localStorage.removeItem('authToken'); localStorage.removeItem('user'); return undefined; } // Restore UI immediately, then prove the JWT still works. Stale tokens // (rotated secret, deleted guest, etc.) previously left the user in chat // with every API call 401'ing and send silently failing. setAuthToken(token); setUser(parsedUser); setIsAuthenticated(true); if (!didSeedRestoredChatHistory) { didSeedRestoredChatHistory = true; applyHistoryState(chatStarterState()); } else { setCurrentView('chat'); setChatNavView('starter'); } (async () => { try { const apiUrl = process.env.REACT_APP_API_URL || ''; const resp = await fetch(`${apiUrl}/auth/me`, { headers: { Authorization: `Bearer ${token}` }, }); if (cancelled) return; if (!resp.ok) { localStorage.removeItem('authToken'); localStorage.removeItem('user'); setUser(null); setAuthToken(null); setIsAuthenticated(false); applyHistoryState(homeState(), { replace: true }); return; } const me = await resp.json(); if (cancelled || !me) return; setUser(me); try { localStorage.setItem('user', JSON.stringify(me)); } catch { /* ignore quota */ } } catch { // Network blip — keep optimistic session; next API call will re-check. } })(); return () => { cancelled = true; }; }, [applyHistoryState]); const navigateToAuth = () => { applyHistoryState({ view: 'auth' }); }; const navigateToJourney = () => { applyHistoryState({ view: 'journey' }); }; const navigateToCanvas = (canvasView) => { if (canvasView === 'journey') { applyHistoryState({ view: 'journey' }); return; } if (['insights', 'workspace', 'deliverables'].includes(canvasView)) { localStorage.setItem('canvas-view-v2', canvasView); } applyHistoryState({ view: 'canvas' }); }; const navigateToChat = () => { applyHistoryState(chatStarterState()); }; const navigateToHome = () => { applyHistoryState(homeState()); }; const handleChatBecameActive = (sessionId) => { applyHistoryState(chatActiveState(sessionId)); }; const handleChatReturnedToStarter = () => { applyHistoryState(chatStarterState(), { replace: true }); }; const handleAuthSuccess = (userData, token) => { setUser(userData); setAuthToken(token); setIsAuthenticated(true); applyHistoryState(chatStarterState(), { replace: historyStateRef.current?.view === 'auth', }); }; const handleSignOut = () => { localStorage.removeItem('authToken'); localStorage.removeItem('user'); try { localStorage.removeItem('canvas-layout-v2'); localStorage.removeItem('canvas-states-v2'); localStorage.removeItem('canvas-deliverables-v2'); localStorage.removeItem('canvas-view-v2'); localStorage.removeItem('canvas-task-status-v1'); } catch { /* ignore */ } setUser(null); setAuthToken(null); setIsAuthenticated(false); applyHistoryState(homeState(), { replace: true }); }; return (
{currentView === 'home' && ( )} {currentView === 'auth' && ( )} {currentView === 'canvas' && isAuthenticated && ( )} {currentView === 'journey' && isAuthenticated && ( )} {currentView === 'chat' && isAuthenticated && ( )}
); } export default App;