import React, { useState, useEffect, useMemo } from 'react'; import { Routes, Route, Navigate, useNavigate, useLocation, Link, } from 'react-router-dom'; import { Button } from './components/ui/button.js'; import { cn } from './utils/cn.js'; import PromptModal from './components/ui/PromptModal.js'; import PremiumRoute from './components/PremiumRoute.js'; import SessionExpiredHandler from './components/SessionExpiredHandler.js'; import SignInLanding from './components/SignInLanding.js'; import SignIn from './components/SignIn.js'; import SignUp from './components/SignUp.js'; import PlanningLanding from './components/PlanningLanding.js'; import CareerExplorer from './components/CareerExplorer.js'; import PreparingLanding from './components/PreparingLanding.js'; import EducationalProgramsPage from './components/EducationalProgramsPage.js'; import EnhancingLanding from './components/EnhancingLanding.js'; import RetirementLanding from './components/RetirementLanding.js'; import InterestInventory from './components/InterestInventory.js'; import UserProfile from './components/UserProfile.js'; import FinancialProfileForm from './components/FinancialProfileForm.js'; import CareerProfileList from './components/CareerProfileList.js'; import CareerProfileForm from './components/CareerProfileForm.js'; import CollegeProfileList from './components/CollegeProfileList.js'; import CollegeProfileForm from './components/CollegeProfileForm.js'; import CareerRoadmap from './components/CareerRoadmap.js'; import Paywall from './components/Paywall.js'; import OnboardingContainer from './components/PremiumOnboarding/OnboardingContainer.js'; import { isOnboardingInProgress } from './utils/onboardingGuard.js'; import RetirementPlanner from './components/RetirementPlanner.js'; import ResumeRewrite from './components/ResumeRewrite.js'; import LoanRepaymentPage from './components/LoanRepaymentPage.js'; import usePageContext from './utils/usePageContext.js'; import ChatDrawer from './components/ChatDrawer.js'; import ChatCtx from './contexts/ChatCtx.js'; import BillingResult from './components/BillingResult.js'; import SupportModal from './components/SupportModal.js'; import ForgotPassword from './components/ForgotPassword.js'; import ResetPassword from './components/ResetPassword.js'; import { clearToken } from './auth/authMemory.js'; import api from './auth/apiClient.js'; import * as safeLocal from './utils/safeLocal.js'; import VerificationGate from './components/VerificationGate.js'; import Verify from './components/Verify.js'; export const ProfileCtx = React.createContext(); function ResetPasswordGate() { const location = useLocation(); useEffect(() => { clearToken(); try { localStorage.removeItem('id'); } catch {} }, [location.pathname]); return ; } function App() { const navigate = useNavigate(); const location = useLocation(); const { pageContext, snapshot: routeSnapshot } = usePageContext(); const [drawerOpen, setDrawerOpen] = useState(false); const [drawerPane, setDrawerPane] = useState('support'); const [retireProps, setRetireProps] = useState(null); const [supportOpen, setSupportOpen] = useState(false); const [loggingOut, setLoggingOut] = useState(false); // Mobile nav const [mobileNavOpen, setMobileNavOpen] = useState(false); const [mobileSection, setMobileSection] = useState(null); // 'planning' | 'preparing' | 'enhancing' | 'retirement' | 'profile' | null const toggleMobileSection = (key) => setMobileSection((k) => (k === key ? null : key)); const AUTH_HOME = '/signin-landing'; const prevPathRef = React.useRef(location.pathname); useEffect(() => { prevPathRef.current = location.pathname; }, [location.pathname]); // Close mobile nav on route change useEffect(() => { setMobileNavOpen(false); }, [location.pathname]); const IN_OB = (p) => p.startsWith('/premium-onboarding'); /* ------------------------------------------ ChatDrawer – route-aware tool handlers ------------------------------------------ */ const uiToolHandlers = useMemo(() => { if (pageContext === "CareerExplorer") { return { // __tool:addCareerToComparison:{"socCode":"15-2051","careerName":"Data Scientist"} addCareerToComparison: ({ socCode, careerName }) => { console.log('[dispatch]', socCode, careerName); window.dispatchEvent( new CustomEvent("add-career", { detail: { socCode, careerName } }) ); }, // __tool:openCareerModal:{"socCode":"15-2051"} openCareerModal: ({ socCode }) => { window.dispatchEvent( new CustomEvent("open-career", { detail: { socCode } }) ); } }; } return {}; // every other page exposes no UI tools }, [pageContext]); // route-change guard: only warn when LEAVING onboarding mid-flow useEffect(() => { const wasIn = IN_OB(prevPathRef.current); const nowIn = IN_OB(location.pathname); const leavingOnboarding = wasIn && !nowIn; if (!leavingOnboarding) return; // skip if not mid-flow or if final handoff set the suppress flag if (!isOnboardingInProgress() || sessionStorage.getItem('suppressOnboardingGuard') === '1') return; const ok = window.confirm( "Onboarding is in progress. If you navigate away, your progress may not be saved. Continue?" ); if (!ok) { // bounce back to where the user was navigate(prevPathRef.current, { replace: true }); } else { // user chose to leave: clear client pointer and server draft immediately try { safeLocal.clearMany(['premiumOnboardingPointer', 'premiumOnboardingState']); } catch {} (async () => { try { await api.delete('/api/premium/onboarding/draft'); } catch {} })(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.pathname]); // browser close/refresh guard: only when currently on onboarding and mid-flow useEffect(() => { const onBeforeUnload = (e) => { if (IN_OB(location.pathname) && isOnboardingInProgress() && sessionStorage.getItem('suppressOnboardingGuard') !== '1') { e.preventDefault(); e.returnValue = "Onboarding is in progress. If you leave now, your progress may not be saved."; } }; window.addEventListener('beforeunload', onBeforeUnload); return () => window.removeEventListener('beforeunload', onBeforeUnload); }, [location.pathname]); // Retirement bot is only relevant on these pages const canShowRetireBot = pageContext === 'RetirementPlanner' || pageContext === 'RetirementLanding'; // Auth states const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState(null); const [chatSnapshot, setChatSnapshot] = useState(null); // Loading state while verifying token const [isLoading, setIsLoading] = useState(true); // User states const [financialProfile, setFinancialProfile] = useState(null); const [scenario, setScenario] = useState(null); // Logout warning modal const [showLogoutWarning, setShowLogoutWarning] = useState(false); // Check if user can access premium const canAccessPremium = user?.is_premium || user?.is_pro_premium; const isAuthScreen = React.useMemo(() => { const p = location.pathname; return ( p === '/signin' || p === '/signup' || p === '/forgot-password' || p.startsWith('/reset-password') ); }, [location.pathname]); const showAuthedNav = isAuthenticated && !isAuthScreen; // List of premium paths for your CTA logic const premiumPaths = [ '/career-roadmap', '/paywall', '/financial-profile', '/retirement-planner', '/premium-onboarding', '/enhancing', '/retirement', '/resume-optimizer', ]; const showPremiumCTA = !premiumPaths.some(p => location.pathname.startsWith(p) ); // ============================== // 1) Single Rehydrate UseEffect // ============================== useEffect(() => { let cancelled = false; if (loggingOut) return; // Skip auth probe on all public auth routes if ( location.pathname.startsWith('/reset-password') || location.pathname === '/signin' || location.pathname === '/signup' || location.pathname === '/forgot-password' ) { try { localStorage.removeItem('id'); } catch {} setIsAuthenticated(false); setUser(null); setIsLoading(false); return; } (async () => { setIsLoading(true); try { // Fetch only the minimal fields App needs for nav/landing/support const { data } = await api.get('/api/user-profile?fields=firstname,is_premium,is_pro_premium'); if (cancelled) return; setUser(prev => ({ ...(prev || {}), firstname : data?.firstname || '', is_premium : !!data?.is_premium, is_pro_premium: !!data?.is_pro_premium, })); setIsAuthenticated(true); } catch (err) { if (cancelled) return; clearToken(); setIsAuthenticated(false); setUser(null); // Only kick to /signin if you’re not already on a public page const p = location.pathname; const onPublic = p === '/signin' || p === '/signup' || p === '/forgot-password' || p.startsWith('/reset-password') || p === '/paywall'; if (!onPublic) navigate('/signin?session=expired', { replace: true }); } finally { if (!cancelled) setIsLoading(false); } })(); return () => { cancelled = true; }; // include isAuthScreen if you prefer, but this local check avoids a dep loop // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.pathname, navigate, loggingOut]); // ========================== // 2) Logout Handler + Modal // ========================== const handleLogoutClick = () => { if (isOnboardingInProgress()) { // Show a modal to confirm losing onboarding data setShowLogoutWarning(true); } else { // No onboarding => just logout confirmLogout(); } }; const confirmLogout = async () => { setLoggingOut(true); // 1) Ask the server to clear the session cookie try { // If you created /logout (no /api prefix): await api.post('/api/logout', {}); // axios client is withCredentials: true // If your route is /api/signout instead, use: // await api.post('/api/signout'); } catch (e) { console.warn('Server logout failed (continuing client-side):', e?.message || e); } // 2) Clear client-side state/caches clearToken(); // in-memory bearer (if any, for legacy flows) safeLocal.clearMany([ 'id', 'careerSuggestionsCache', 'lastSelectedCareerProfileId', 'selectedCareer', 'aiClickCount', 'aiClickDate', 'aiRecommendations', 'premiumOnboardingState', 'premiumOnboardingPointer', 'financialProfile', 'selectedScenario', ]); // 3) Reset React state setFinancialProfile(null); setScenario(null); setIsAuthenticated(false); setUser(null); setShowLogoutWarning(false); // 4) Back to sign-in navigate('/signin', { replace: true }); setLoggingOut(false); }; const cancelLogout = () => { setShowLogoutWarning(false); }; // ==================================== // 3) If still verifying the token, show loading // ==================================== if (isLoading) { return (

Loading...

); } // ===================== // Main Render / Layout // ===================== return ( { if (!isAuthenticated) return; setDrawerPane('support'); setDrawerOpen(true); }, openRetire : (props) => { if (!isAuthenticated || !canShowRetireBot) return; if (!canShowRetireBot) { console.warn('Retirement bot disabled on this page'); return; } setRetireProps(props); setDrawerPane('retire'); setDrawerOpen(true); } }}>
{/* Header */}

AptivaAI - Career Guidance Platform

{/* Mobile hamburger */} {showAuthedNav && ( <> {/* Desktop NAV */} {/* LOGOUT + UPGRADE BUTTONS (desktop) */}
{showPremiumCTA && !canAccessPremium && ( )} {/* LOGOUT BUTTON */} {/* SHOW WARNING MODAL IF needed */} {showLogoutWarning && ( )}
)}
{/* Mobile slide-down menu */} {showAuthedNav && (
{/* Upgrade CTA (mobile) */} {showPremiumCTA && !canAccessPremium && ( )} {/* Primary sections (touch-friendly, no shadcn Button to avoid style overrides) */} {mobileSection === 'planning' && (
Find Your Career — Overview Career Explorer Interest Inventory
)} {mobileSection === 'preparing' && (
Preparing — Overview Educational Programs Education Repayment Calculator
)} {mobileSection === 'enhancing' && (
Enhancing — Overview {!canAccessPremium && (Premium)} Roadmap & AI Career Coach Optimize Resume
)} {mobileSection === 'retirement' && (
Retirement — Overview {!canAccessPremium && (Premium)}
)} {mobileSection === 'profile' && (
Account Financial Profile {canAccessPremium ? ( Career Profiles ) : ( Career Profiles (Premium) )} {canAccessPremium ? ( College Profiles ) : ( College Profiles (Premium) )}
)} {/* Support + Logout */}
)} {/* MAIN CONTENT */}
{/* Default */} } /> } /> {/* Public (guest-only) routes */} ) : ( ) } /> : } /> : } /> } /> } /> {/* Authenticated routes */} {isAuthenticated && ( <> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Premium-wrapped */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> )} {/* 404 / Fallback */} } />
{/* Support modal mounted once at root so it centers correctly on desktop & mobile */} setSupportOpen(false)} /> {isAuthenticated && ( )} {/* Session Handler (Optional) */}
); } export default App;