dev1/src/App.js
Josh e25662aae4
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
Branding/UI & Loan Repayment fixes
2025-09-23 14:25:18 +00:00

903 lines
35 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <ResetPassword />;
}
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 youre 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 (
<div className="flex min-h-[100dvh] items-center justify-center">
<p>Loading...</p>
</div>
);
}
// =====================
// Main Render / Layout
// =====================
return (
<ProfileCtx.Provider
value={{ financialProfile, setFinancialProfile,
scenario, setScenario,
user, setUser}}
>
<ChatCtx.Provider value={{ setChatSnapshot,
openSupport: () => {
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);
}
}}>
<div className="flex min-h-[100dvh] flex-col bg-gray-50 text-gray-800">
{/* Header */}
<header className="sticky top-0 z-40 flex items-center justify-between border-b bg-white/95 px-4 py-3 md:px-6 md:py-4 backdrop-blur supports-[backdrop-filter]:bg-white/70">
<h1 className="text-base md:text-lg font-semibold truncate pr-3">
AptivaAI - Career Guidance Platform
</h1>
{/* Mobile hamburger */}
<button
type="button"
aria-label="Open menu"
className="md:hidden inline-flex items-center justify-center rounded-md p-2 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-600"
onClick={() => setMobileNavOpen(v => !v)}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="h-6 w-6">
<path d="M3.75 6.75h16.5M3.75 12h16.5M3.75 17.25h16.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
</svg>
</button>
{showAuthedNav && (
<>
{/* Desktop NAV */}
<nav className="hidden md:flex md:space-x-6">
{/* 1) Planning */}
<div className="relative group">
<Button
style={{ color: '#1f2937' }}
className={cn(
'bg-white',
'border border-gray-300',
'hover:bg-gray-100',
'hover:text-blue-700',
'whitespace-nowrap',
'text-xs sm:text-sm md:text-base',
'font-semibold'
)}
onClick={() => navigate('/planning')}
>
Find Your Career
</Button>
<div className="absolute top-full left-0 hidden group-hover:block bg-white border shadow-md w-48 z-50">
<Link
to="/career-explorer"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Career Explorer
</Link>
<Link
to="/interest-inventory"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Interest Inventory
</Link>
</div>
</div>
{/* 2) Preparing */}
<div className="relative group">
<Button
style={{ color: '#1f2937' }}
className={cn(
'bg-white',
'border border-gray-300',
'hover:bg-gray-100',
'hover:text-blue-700',
'whitespace-nowrap',
'text-xs sm:text-sm md:text-base',
'font-semibold'
)}
onClick={() => navigate('/preparing')}
>
Preparing & UpSkilling for Your Career
</Button>
<div className="absolute top-full left-0 hidden group-hover:block bg-white border shadow-md w-56 z-50">
<Link
to="/educational-programs"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Educational Programs
</Link>
<Link
to="/preparing?loan=1"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Education Repayment Calculator
</Link>
</div>
</div>
{/* 3) Enhancing (Premium) */}
<div className="relative group">
<Button
style={{ color: '#1f2937' }}
className={cn(
'bg-white',
'border border-gray-300',
'hover:bg-gray-100',
'hover:text-blue-700',
'whitespace-nowrap',
'text-xs sm:text-sm md:text-base',
'font-semibold'
)}
onClick={() => navigate('/enhancing')}
>
Enhancing Your Career
{!canAccessPremium && (
<span className="text-xs ml-1 text-gray-600">
(Premium)
</span>
)}
</Button>
<div className="absolute top-full left-0 hidden group-hover:block bg-white border shadow-md w-56 z-50">
<Link
to="/career-roadmap"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Roadmap & AI Career Coach
</Link>
<Link
to="/resume-optimizer"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Optimize Resume
</Link>
{/* etc. */}
</div>
</div>
{/* 4) Retirement (Premium) */}
<div className="relative group">
<Button
style={{ color: '#1f2937' }}
className={cn(
'bg-white',
'border border-gray-300',
'hover:bg-gray-100',
'hover:text-blue-700',
'whitespace-nowrap',
'text-xs sm:text-sm md:text-base',
'font-semibold'
)}
onClick={() => navigate('/retirement')}
>
Retirement Planning (beta)
{!canAccessPremium && (
<span className="text-xs ml-1 text-gray-600">
(Premium)
</span>
)}
</Button>
<div className="absolute top-full left-0 hidden group-hover:block bg-white border shadow-md w-56 z-50">
{/* Additional retirement menu items */}
</div>
</div>
{/* 5) Profile */}
<div className="relative group">
<Button
style={{ color: '#1f2937' }}
className={cn(
'bg-white',
'border border-gray-300',
'hover:bg-gray-100',
'hover:text-blue-700',
'whitespace-nowrap',
'text-xs sm:text-sm md:text-base',
'font-semibold',
'min-w-0',
'max-w-[90px]',
'truncate'
)}
>
Profile
</Button>
<div className="absolute top-full left-0 hidden group-hover:block bg-white border shadow-md w-48 z-50">
<Link
to="/profile"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Account
</Link>
<Link
to="/financial-profile"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Financial Profile
</Link>
{canAccessPremium ? (
/* Premium users go straight to the wizard */
<Link
to="/profile/careers"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Career Profiles
</Link>
) : (
<span
className="block px-4 py-2 text-sm text-gray-400 cursor-not-allowed"
>
Career Profiles (Premium)
</span>
)}
{/* College Profiles (go straight to list) */}
{canAccessPremium ? (
<Link
to="/profile/college"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
College Profiles
</Link>
) : (
<span className="block px-4 py-2 text-sm text-gray-400 cursor-not-allowed">
College Profiles (Premium)
</span>
)}
</div>
</div>
</nav>
{/* LOGOUT + UPGRADE BUTTONS (desktop) */}
<div className="hidden md:flex items-center space-x-4 ml-4 relative z-10">
{showPremiumCTA && !canAccessPremium && (
<Button
className={cn(
'bg-green-500 hover:bg-green-600',
'max-w-fit text-center text-white',
'px-3 py-2',
'rounded',
'whitespace-nowrap',
'text-sm',
'font-semibold',
'shadow'
)}
style={{ minWidth: 0, width: 'auto' }}
onClick={() => navigate('/paywall')}
>
Upgrade to Premium
</Button>
)}
<button
type="button"
onClick={() => setSupportOpen(true)}
className="px-3 py-1 rounded hover:bg-gray-100"
>
Support
</button>
{/* LOGOUT BUTTON */}
<button
onClick={handleLogoutClick}
disabled={loggingOut}
className="text-red-600 hover:text-red-800 bg-transparent border-none disabled:opacity-60"
>
{loggingOut ? 'Signing out…' : 'Logout'}
</button>
{/* SHOW WARNING MODAL IF needed */}
{showLogoutWarning && (
<PromptModal
open={true}
title="End Onboarding?"
message="If you sign out now, your onboarding progress will be lost. Are you sure?"
confirmText="Sign Out"
cancelText="Nevermind"
onConfirm={confirmLogout}
onCancel={cancelLogout}
/>
)}
</div>
</>
)}
</header>
{/* Mobile slide-down menu */}
{showAuthedNav && (
<div
className={cn(
"md:hidden border-b bg-white shadow-sm",
mobileNavOpen ? "block" : "hidden"
)}
>
<div className="px-4 py-3 space-y-2">
{/* Upgrade CTA (mobile) */}
{showPremiumCTA && !canAccessPremium && (
<Button
className={cn('bg-green-500 hover:bg-green-600 text-white w-full h-12')}
onClick={() => navigate('/paywall')}
>
Upgrade to Premium
</Button>
)}
{/* Primary sections (touch-friendly, no shadcn Button to avoid style overrides) */}
<button
type="button"
onClick={() => toggleMobileSection('planning')}
className="w-full h-12 px-3 inline-flex items-center justify-between rounded border text-gray-800 bg-white active:bg-gray-50"
>
<span className="text-base">Find Your Career</span>
<span className={cn("transition-transform", mobileSection==='planning' ? "rotate-180" : "")}></span>
</button>
{mobileSection === 'planning' && (
<div className="pl-3 space-y-1">
<Link
to="/planning"
className="block px-2 py-2 text-sm font-medium text-blue-700 rounded hover:bg-blue-50"
>
Find Your Career Overview
</Link>
<Link to="/career-explorer" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Career Explorer</Link>
<Link to="/interest-inventory" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Interest Inventory</Link>
</div>
)}
<button
type="button"
onClick={() => toggleMobileSection('preparing')}
className="w-full h-12 px-3 inline-flex items-center justify-between rounded border text-gray-800 bg-white active:bg-gray-50"
>
<span className="text-base">Preparing &amp; UpSkilling for Your Career</span>
<span className={cn("transition-transform", mobileSection==='preparing' ? "rotate-180" : "")}></span>
</button>
{mobileSection === 'preparing' && (
<div className="pl-3 space-y-1">
<Link
to="/preparing"
className="block px-2 py-2 text-sm font-medium text-blue-700 rounded hover:bg-blue-50"
>
Preparing Overview
</Link>
<Link to="/educational-programs" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Educational Programs</Link>
<Link to="/preparing?loan=1" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Education Repayment Calculator</Link>
</div>
)}
<button
type="button"
onClick={() => toggleMobileSection('enhancing')}
className="w-full h-12 px-3 inline-flex items-center justify-between rounded border text-gray-800 bg-white active:bg-gray-50"
>
<span className="text-base">
Enhancing Your Career {!canAccessPremium && <span className="ml-1 text-xs text-gray-600">(Premium)</span>}
</span>
<span className={cn("transition-transform", mobileSection==='enhancing' ? "rotate-180" : "")}></span>
</button>
{mobileSection === 'enhancing' && (
<div className="pl-3 space-y-1">
<Link
to="/enhancing"
className="block px-2 py-2 text-sm font-medium text-blue-700 rounded hover:bg-blue-50"
>
Enhancing Overview {!canAccessPremium && <span className="ml-1 text-xs text-gray-500">(Premium)</span>}
</Link>
<Link to="/career-roadmap" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Roadmap &amp; AI Career Coach</Link>
<Link to="/resume-optimizer" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Optimize Resume</Link>
</div>
)}
<button
type="button"
onClick={() => toggleMobileSection('retirement')}
className="w-full h-12 px-3 inline-flex items-center justify-between rounded border text-gray-800 bg-white active:bg-gray-50"
>
<span className="text-base">
Retirement Planning (beta) {!canAccessPremium && <span className="ml-1 text-xs text-gray-600">(Premium)</span>}
</span>
<span className={cn("transition-transform", mobileSection==='retirement' ? "rotate-180" : "")}></span>
</button>
{mobileSection === 'retirement' && (
<div className="pl-3 space-y-1">
<Link
to="/retirement"
className="block px-2 py-2 text-sm font-medium text-blue-700 rounded hover:bg-blue-50"
>
Retirement Overview {!canAccessPremium && <span className="ml-1 text-xs text-gray-500">(Premium)</span>}
</Link>
</div>
)}
<button
type="button"
onClick={() => toggleMobileSection('profile')}
className="w-full h-12 px-3 inline-flex items-center justify-between rounded border text-gray-800 bg-white active:bg-gray-50"
>
<span className="text-base">Profile</span>
<span className={cn("transition-transform", mobileSection==='profile' ? "rotate-180" : "")}></span>
</button>
{mobileSection === 'profile' && (
<div className="pl-3 space-y-1">
<Link to="/profile" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Account</Link>
<Link to="/financial-profile" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Financial Profile</Link>
{canAccessPremium ? (
<Link to="/profile/careers" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">Career Profiles</Link>
) : (
<span className="block px-2 py-2 text-sm text-gray-400">Career Profiles (Premium)</span>
)}
{canAccessPremium ? (
<Link to="/profile/college" className="block px-2 py-2 text-sm text-gray-700 rounded hover:bg-gray-100">College Profiles</Link>
) : (
<span className="block px-2 py-2 text-sm text-gray-400">College Profiles (Premium)</span>
)}
</div>
)}
{/* Support + Logout */}
<div className="flex items-center gap-3 pt-2">
<button
type="button"
onClick={() => setSupportOpen(true)}
className="px-3 py-1 rounded hover:bg-gray-100"
>
Support
</button>
<button
onClick={handleLogoutClick}
disabled={loggingOut}
className="flex-1 px-3 py-3 rounded border border-red-300 text-red-600 hover:bg-red-50 disabled:opacity-60"
>
{loggingOut ? 'Signing out…' : 'Logout'}
</button>
</div>
</div>
</div>
)}
{/* MAIN CONTENT */}
<main className="flex-1 p-4 md:p-6">
<Routes>
{/* Default */}
<Route
path="/"
element={<Navigate to={isAuthenticated ? AUTH_HOME : '/signin'} replace />}
/>
<Route path="/reset-password/:token" element={<ResetPasswordGate />} />
{/* Public (guest-only) routes */}
<Route
path="/signin"
element={
isAuthenticated ? (
<Navigate to={AUTH_HOME} replace />
) : (
<SignIn
setIsAuthenticated={setIsAuthenticated}
setUser={setUser}
setFinancialProfile={setFinancialProfile}
setScenario={setScenario}
/>
)
}
/>
<Route
path="/signup"
element={isAuthenticated ? <Navigate to={AUTH_HOME} replace /> : <SignUp setUser={setUser} />}
/>
<Route
path="/forgot-password"
element={isAuthenticated ? <Navigate to={AUTH_HOME} replace /> : <ForgotPassword />}
/>
<Route path="/paywall" element={<Paywall />} />
<Route path="/verify" element={<Verify />} />
{/* Authenticated routes */}
{isAuthenticated && (
<>
<Route path="/signin-landing" element={<VerificationGate><SignInLanding user={user} /></VerificationGate>} />
<Route path="/interest-inventory" element={<VerificationGate><InterestInventory /></VerificationGate>} />
<Route path="/profile" element={<VerificationGate><UserProfile /></VerificationGate>} />
<Route path="/planning" element={<VerificationGate><PlanningLanding /></VerificationGate>} />
<Route path="/career-explorer" element={<VerificationGate><CareerExplorer /></VerificationGate>} />
<Route path="/loan-repayment" element={<VerificationGate><LoanRepaymentPage /></VerificationGate>} />
<Route path="/educational-programs" element={<VerificationGate><EducationalProgramsPage /></VerificationGate>} />
<Route path="/preparing" element={<VerificationGate><PreparingLanding /></VerificationGate>} />
<Route path="/billing" element={<BillingResult />} />
{/* Premium-wrapped */}
<Route path="/enhancing" element={<VerificationGate><PremiumRoute user={user}><EnhancingLanding /></PremiumRoute></VerificationGate>} />
<Route path="/retirement" element={<VerificationGate><PremiumRoute user={user}><RetirementLanding /></PremiumRoute></VerificationGate>} />
<Route path="/career-roadmap/:careerId?" element={<VerificationGate><PremiumRoute user={user}><CareerRoadmap /></PremiumRoute></VerificationGate>} />
<Route path="/profile/careers" element={<VerificationGate><CareerProfileList /></VerificationGate>} />
<Route path="/profile/careers/:id/edit" element={<VerificationGate><CareerProfileForm /></VerificationGate>} />
<Route path="/profile/college" element={<VerificationGate><CollegeProfileList /></VerificationGate>} />
<Route path="/profile/college/:careerId/:id?" element={<VerificationGate><CollegeProfileForm /></VerificationGate>} />
<Route path="/financial-profile" element={<VerificationGate><PremiumRoute user={user}><FinancialProfileForm /></PremiumRoute></VerificationGate>} />
<Route path="/retirement-planner" element={<VerificationGate><PremiumRoute user={user}><RetirementPlanner /></PremiumRoute></VerificationGate>} />
<Route path="/premium-onboarding" element={<VerificationGate><PremiumRoute user={user}><OnboardingContainer /></PremiumRoute></VerificationGate>} />
<Route path="/resume-optimizer" element={<VerificationGate><PremiumRoute user={user}><ResumeRewrite /></PremiumRoute></VerificationGate>} />
</>
)}
{/* 404 / Fallback */}
<Route
path="*"
element={<Navigate to={isAuthenticated ? AUTH_HOME : '/signin'} replace />}
/>
</Routes>
</main>
{/* Support modal mounted once at root so it centers correctly on desktop & mobile */}
<SupportModal
open={supportOpen}
onClose={() => setSupportOpen(false)}
/>
{isAuthenticated && (
<ChatDrawer
open={drawerOpen}
onOpenChange={setDrawerOpen}
pane={drawerPane}
setPane={setDrawerPane}
retireProps={retireProps}
pageContext={pageContext}
snapshot={chatSnapshot}
uiToolHandlers={uiToolHandlers}
canShowRetireBot={canShowRetireBot}
/>
)}
{/* Session Handler (Optional) */}
<SessionExpiredHandler />
</div>
</ChatCtx.Provider>
</ProfileCtx.Provider>
);
}
export default App;