From 0a57a9701623a14a59a7099111d24680b77e87c1 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 22 Sep 2025 15:27:42 +0000 Subject: [PATCH] Mobile UI optimizations --- .build.hash | 2 +- src/App.js | 199 ++++++++++++++++++++-- src/components/CareerExplorer.js | 81 ++++++--- src/components/ChatDrawer.js | 2 +- src/components/EducationalProgramsPage.js | 29 ++-- src/components/InterestInventory.js | 2 +- src/components/SignInLanding.js | 20 ++- src/utils/net.js | 37 ++++ 8 files changed, 308 insertions(+), 64 deletions(-) create mode 100644 src/utils/net.js diff --git a/.build.hash b/.build.hash index df5c75a..d179af5 100644 --- a/.build.hash +++ b/.build.hash @@ -1 +1 @@ -7c4503634c1566a112e17705d07e15f792647175-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b +f60aaeaf5d529bc05bbbd41815d65d86e9010dfc-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b diff --git a/src/App.js b/src/App.js index 9b3c3dd..056d600 100644 --- a/src/App.js +++ b/src/App.js @@ -72,6 +72,10 @@ function App() { 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'; @@ -79,6 +83,9 @@ function App() { 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'); /* ------------------------------------------ @@ -323,7 +330,7 @@ const cancelLogout = () => { // ==================================== if (isLoading) { return ( -
+

Loading...

); @@ -355,17 +362,29 @@ const cancelLogout = () => { setDrawerOpen(true); } }}> -
+
{/* Header */} -
-

+
+

AptivaAI - Career Guidance Platform

+ {/* Mobile hamburger */} + + {showAuthedNav && ( <> - {/* NAV MENU */} -
+{/* 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 +
+ )} + + + {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 */} {
+ {/* Support modal mounted once at root so it centers correctly on desktop & mobile */} + setSupportOpen(false)} + /> + {isAuthenticated && ( { + const src = Array.isArray(careerSuggestions) ? careerSuggestions : []; + if (!selectedJobZone && !selectedFit) return src; + return src.filter((c) => { + const zoneMatch = selectedJobZone ? String(c?.job_zone ?? '') === String(selectedJobZone) : true; + const fitMatch = selectedFit ? String(c?.fit ?? '') === String(selectedFit) : true; + return zoneMatch && fitMatch; + }); + }, [careerSuggestions, selectedJobZone, selectedFit]); + // ---------- Load cache on mount (no profile call) ---------- useEffect(() => { const cached = localStorage.getItem('careerSuggestionsCache'); @@ -547,7 +558,7 @@ function CareerExplorer() { // ---- Render return ( -
+
{renderLoadingOverlay()} {showModal && ( @@ -588,8 +599,10 @@ function CareerExplorer() {
{careerList.length ? ( - - + /* Mobile: contain + allow horizontal scroll so the table never bleeds past card */ +
+
+ {prioritiesKeys.map((k) => ( @@ -649,12 +662,17 @@ function CareerExplorer() { - ); })} -
Career{balanceRating} {recognitionRating} {matchScore.toFixed(1)}% - - +
+ + + }} + > + Plan your Education/Skills + +
- ) : (

No careers added to comparison.

)} + +
+ ) : (

No careers added to comparison.

)} -
- setSelectedJobZone(e.target.value)} + > {Object.entries(jobZoneLabels).map(([zone, label]) => ( ))} - setSelectedFit(e.target.value)} + > {Object.entries(fitLabels).map(([key, label]) => ( ))} - -
- ⚠️ - = May have limited data for this career path -
+
+ +{/* Warning note: separate row on mobile to avoid squish */} +
+ ⚠️ + = May have limited data for this career path
{ setSelectedCareer(career); handleCareerClick(career); }} /> diff --git a/src/components/ChatDrawer.js b/src/components/ChatDrawer.js index 5b7d2d7..9f4e3d1 100644 --- a/src/components/ChatDrawer.js +++ b/src/components/ChatDrawer.js @@ -182,7 +182,7 @@ export default function ChatDrawer({ {/* side drawer */} {/* header (tabs only if retirement bot is allowed) */}
diff --git a/src/components/EducationalProgramsPage.js b/src/components/EducationalProgramsPage.js index 1f65b98..764f50a 100644 --- a/src/components/EducationalProgramsPage.js +++ b/src/components/EducationalProgramsPage.js @@ -455,16 +455,17 @@ const topSchools = filteredAndSortedSchools.slice(0, TOP_N).map(s => ({ const links = !isAbility ? getSearchLinks(elementName, careerTitle) : null; const definition = ONET_DEFINITIONS[elementName] || 'No definition available'; - return ( + return ( - {elementName}{' '} - - i + + {elementName} + + i + {impStars} @@ -564,18 +565,16 @@ const topSchools = filteredAndSortedSchools.slice(0, TOP_N).map(s => ({ Ability Importance Level - - {/* Info icon for "Why no courses?" */} - - Why no courses? + +
+ Why no courses? i - +
diff --git a/src/components/InterestInventory.js b/src/components/InterestInventory.js index 118f53a..99ef479 100644 --- a/src/components/InterestInventory.js +++ b/src/components/InterestInventory.js @@ -23,7 +23,7 @@ const InterestInventory = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [userProfile, setUserProfile] = useState(null); - const isProd = (process.env.REACT_APP_ENV_NAME || '').toLowerCase() === 'prod'; + const isProd = (process.env.ENV_NAME || '').toLowerCase() === 'prod'; const navigate = useNavigate(); diff --git a/src/components/SignInLanding.js b/src/components/SignInLanding.js index 501a26e..468ca1d 100644 --- a/src/components/SignInLanding.js +++ b/src/components/SignInLanding.js @@ -4,16 +4,17 @@ import { Link } from 'react-router-dom'; function SignInLanding({ user }) { return ( -
-

- Welcome to AptivaAI {user?.firstname}! -

+
+

+ Welcome to AptivaAI {user?.firstname}! +

At AptivaAI, we aim to arm you with as much information as possible to make informed career decisions. Today’s workplace is changing faster than ever, driven largely by AI—but our goal is to use that same technology to empower job seekers, not replace them. We blend data-backed insights with human-centered design, enhanced -not driven by- AI. Giving you practical recommendations and real-world context so you stay in the driver’s seat of your career. Whether you’re planning your first step, advancing your current role, or ready to pivot entirely, our platform keeps you in control—helping you adapt, grow, and thrive on your own terms.

    +
  • Planning: Just starting out? Looking for a different career that is a better fit? Explore options and figure out what careers match your interests and skills.
  • Preparing: Know what you want but just not how to get there? Gain education, skills, or certifications required to start or transition.
  • Enhancing: You've got some experience in your field but want to know how to get to the next level? Advance, seek promotions, or shift roles for an established professional.
  • @@ -22,17 +23,18 @@ We blend data-backed insights with human-centered design, enhanced -not driven b

    Where would you like to go next?

    -
    - + {/* Mobile: stacked full-width; Desktop: original inline buttons with spacing */} +
    + Go to Exploring - + Go to Preparing - + Go to Enhancing - + Go to Retirement
    diff --git a/src/utils/net.js b/src/utils/net.js new file mode 100644 index 0000000..6671bde --- /dev/null +++ b/src/utils/net.js @@ -0,0 +1,37 @@ +// src/utils/net.js +export function computeNetState() { + if (typeof navigator === 'undefined') return { slow: false, effectiveType: '', downlink: 0 }; + const nc = navigator.connection || navigator.webkitConnection || navigator.mozConnection; + const effectiveType = nc?.effectiveType || ''; + const downlink = Number(nc?.downlink || 0); + const slow = ['slow-2g', '2g', '3g'].includes(effectiveType) || (downlink > 0 && downlink < 3); + return { slow, effectiveType, downlink }; +} + +export function getNetState() { + // Fallback keeps things safe in SSR or older browsers + return (typeof window !== 'undefined' && window.__APT_NET_STATE) || { slow: false, effectiveType: '', downlink: 0 }; +} + +export function setNetState(s) { + if (typeof window !== 'undefined') window.__APT_NET_STATE = s; +} + +export function initNetObserver() { + const update = () => setNetState(computeNetState()); + update(); // seed immediately + + const nc = (typeof navigator !== 'undefined') && (navigator.connection || navigator.webkitConnection || navigator.mozConnection); + if (nc && nc.addEventListener) { + nc.addEventListener('change', update); + return () => nc.removeEventListener('change', update); + } + // Some browsers use onChange + if (nc && 'onchange' in nc) { + const handler = () => update(); + nc.onchange = handler; + return () => { if (nc.onchange === handler) nc.onchange = null; }; + } + // No-op cleanup + return () => {}; +}