Branding/UI & Loan Repayment fixes
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful

This commit is contained in:
Josh 2025-09-23 14:25:18 +00:00
parent 5a0c7efd25
commit e25662aae4
10 changed files with 463 additions and 273 deletions

View File

@ -1 +1 @@
77b19da169acfc9f13cfa14f2fb425fea6c03ef4-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b
e520c3d4f21d892f230efe5f06e338b842191dd6-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b

View File

@ -182,6 +182,7 @@ services:
networks: [default, aptiva-shared]
environment:
GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY}
ENV_NAME: ${ENV_NAME}
ports: ["80:80", "443:443"]
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro

View File

@ -442,6 +442,12 @@ const cancelLogout = () => {
>
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>
@ -689,6 +695,7 @@ const cancelLogout = () => {
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>
)}

View File

@ -1,64 +1,60 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { Button } from "./ui/button.js";
import EconomicProjections from "./EconomicProjections.js";
/* simple pill-style label */
const Chip = ({ label }) => (
<span
className="
inline-flex items-center text-sm px-3 py-1
rounded-full bg-gray-100 text-gray-700
border border-gray-300
"
>
{label}
<span className="ml-1 text-xs text-green-600"> in Career Coach</span>
</span>
);
export default function EnhancingLanding({ userProfile }) {
const navigate = useNavigate();
const socCode = userProfile?.socCode;
const stateName = userProfile?.state;
return (
<div className="min-h-screen bg-gray-50 py-8 px-4">
<div className="max-w-4xl mx-auto space-y-10">
<div className="min-h-screen bg-gray-50 py-10 px-4">
<div className="max-w-3xl mx-auto bg-white rounded-2xl shadow p-6 md:p-8">
{/* Title + intro (centered, blue headline) */}
<h1 className="text-3xl font-bold text-blue-600 text-center">
Enhancing Your Career
</h1>
<p className="text-center text-gray-600 mt-2">
Build momentum in your current role and prepare for your next step.
Use Career Coach for guided milestones, and fine-tune your materials
with the Resume Optimizer.
</p>
{/* 📌 Current status */}
<section className="bg-white shadow rounded-lg p-6">
<h2 className="text-2xl font-semibold mb-4">
📌 Your Current Status &amp; Next Steps
</h2>
<p className="text-gray-600 mb-4">
Evaluate where you are in your career and discover upcoming milestones with our AI&nbsp;recommendations.
</p>
<EconomicProjections socCode={socCode} stateName={stateName} />
<div className="mt-4">
<Button onClick={() => navigate("/career-roadmap")}>
Open Career Coach
</Button>
</div>
</section>
{/* 🚀 How do I get there */}
<section className="bg-white shadow rounded-lg p-6">
<h2 className="text-2xl font-semibold mb-4">🚀 How Do I Get There?</h2>
<div className="flex flex-wrap gap-3">
<Chip label="Resume Optimizer" />
<Chip label="Networking" />
<Chip label="Interview Help" />
<Chip label="Job Search" />
{/* Feature tiles (inside the main card) */}
<div className="grid gap-6 md:gap-8 md:grid-cols-2 mt-6">
{/* Career Coach */}
<div className="rounded-xl border border-gray-200 p-5">
<h3 className="text-lg font-medium">Career Coach</h3>
<p className="text-gray-600 mt-1">
Guided milestones for networking, interview prep, and job search.
</p>
<div className="mt-4">
<Button size="lg" onClick={() => navigate("/career-roadmap")}>
Open Career Coach
</Button>
</div>
</div>
<p className="text-sm text-gray-500 mt-3">
All of these tools live inside the <strong>Career Coach</strong>. Open it any time using the button above.
</p>
</section>
{/* Resume Optimizer (separate tool) */}
<div className="rounded-xl border border-gray-200 p-5">
<h3 className="text-lg font-medium">Resume Optimizer</h3>
<p className="text-gray-600 mt-1">
Improve wording, keywords, and formatting to match target roles.
</p>
<div className="mt-4">
<Button size="lg" onClick={() => navigate("/resume-optmizer")}>
Open Resume Optimizer
</Button>
</div>
</div>
</div>
{/* Divider + lower subsection to mirror the secondary blocks on other landings */}
<hr className="my-6 border-gray-200" />
<h2 className="text-xl font-semibold">Your Current Status & Next Steps</h2>
<p className="text-gray-600 mt-1">
Evaluate where you are in your career and discover upcoming milestones
with our AI recommendations.
</p>
</div>
</div>

View File

@ -6,6 +6,8 @@ import { Button } from './ui/button.js';
import { getNearbySchools } from '../utils/getNearbySchools.js';
import UpsellSummary from './UpsellSummary.js';
import { useNavigate } from 'react-router-dom';
import api from '../auth/apiClient.js';
/* ───────────────────────── CONSTANTS ───────────────────────── */
const DEGREE_OPTS = [
@ -32,9 +34,21 @@ export default function LoanRepaymentDrawer({
}) {
/* Hooks must always run return null later if !open */
/* ── Remote data for auto-suggest ─ */
const [cipData, setCipData] = useState([]);
const [schoolSearch, setSchoolSearch] = useState('');
const [icData, setIcData] = useState([]);
const [selectedSchool, setSelectedSchool] = useState({ name: '', unitId: null });
const [schoolSug, setSchoolSug] = useState([]); // [{name, unitId}]
const [schoolSugOpen, setSchoolSugOpen] = useState(false);
const [schoolHi, setSchoolHi] = useState(-1);
// Program auto-suggest (depends on selectedSchool)
const [programSearch, setProgramSearch] = useState('');
const [selectedProgram, setSelectedProgram] = useState(''); // exact string
const [programSug, setProgramSug] = useState([]); // [{program}]
const [programSugOpen, setProgramSugOpen] = useState(false);
const [programHi, setProgramHi] = useState(-1);
// Degree types (filtered by school + program)
const [degreeTypes, setDegreeTypes] = useState([]); // ['Bachelor's Degree', ...]
/* ── Simple form fields ─ */
const [degree, setDegree] = useState('');
@ -62,106 +76,114 @@ const getAnnualTuition = (schoolsArr, typed, isGrad, resid) =>
pickField(schoolsArr?.[0] ?? {}, isGrad, resid) ||
(typed ? Number(typed) : 0);
// Degree menu: prefer backend-provided types (if any), else fall back
const degreeMenu = degreeTypes.length ? degreeTypes : DEGREE_OPTS;
/* ── memoed degree list for current school ── */
const schoolDegrees = useMemo(() => {
if (!schoolSearch.trim()) return [];
const list = cipData
.filter(r => r.INSTNM.toLowerCase() === schoolSearch.toLowerCase())
.map(r => r.CREDDESC);
return [...new Set(list)];
}, [schoolSearch, cipData]);
const degreeMenu = schoolDegrees.length ? schoolDegrees : DEGREE_OPTS;
const isGrad = /(Master|Doctoral|First Professional|Graduate|Certificate)/i.test(degree);
const annualTuition= getAnnualTuition(schools, tuition, isGrad, tuitionType);
const showUpsell = user && !user.is_premium && !user.is_pro_premium;
useEffect(() => {
useEffect(() => {
if (!open) return;
if (schools.length) return;
if (!cipCodes.length) return;
(async () => {
try {
const seed = await getNearbySchools(cipCodes, userZip, userState);
if (seed.length) setSchools(seed);
} catch (e) {
console.warn('auto-seed schools failed:', e);
}
} catch (e) { console.warn('auto-seed schools failed:', e); }
})();
}, [open, schools.length, cipCodes.join('-'), userZip, userState, setSchools]);
/*
FETCH CIP DATA (only once the drawer is ever opened)
*/
useEffect(() => {
if (!open || cipData.length) return;
fetch('/cip_institution_mapping_new.json')
.then(r => r.text())
.then(text =>
text
.split('\n')
.map(l => { try { return JSON.parse(l); } catch { return null; } })
.filter(Boolean)
)
.then(arr => setCipData(arr))
.catch(e => console.error('CIP fetch error', e));
}, [open, cipData.length]);
/* ───────── SCHOOL SUGGEST (server) ───────── */
/*
SCHOOL AUTOCOMPLETE LIST (memoised)
*/
const suggestions = useMemo(() => {
if (!schoolSearch.trim()) return [];
const low = schoolSearch.toLowerCase();
const set = new Set(
cipData
.filter(r => r.INSTNM.toLowerCase().includes(low))
.map(r => r.INSTNM)
);
return [...set].slice(0, 10);
}, [schoolSearch, cipData]);
useEffect(() => {
if (!open || icData.length) return;
fetch('/ic2023_ay.csv')
.then(r => r.text())
.then(text => {
const [header, ...rows] = text.split('\n').map(l => l.split(','));
return rows.map(row =>
Object.fromEntries(row.map((v, i) => [header[i], v]))
);
})
.then(setIcData)
.catch(e => console.error('iPEDS load fail', e));
}, [open, icData.length]);
/* ───────── auto-tuition when schoolSearch settles ───────── */
useEffect(() => {
if (!schoolSearch.trim() || !icData.length) return;
const rec = cipData.find(r => r.INSTNM.toLowerCase() === schoolSearch.toLowerCase());
if (!rec) return;
const match = icData.find(r => r.UNITID === rec.UNITID);
if (!match) return;
const calc = () => {
const grad = /(Master|Doctoral|First Professional|Graduate|Certificate)/i.test(degree);
if (!grad) {
return tuitionType === 'inState'
? parseFloat(match.TUITION1 || match.TUITION2 || '')
: parseFloat(match.TUITION3 || '');
if (!open) return;
const q = schoolSearch.trim();
if (q.length < 2) { setSchoolSug([]); return; }
let cancelled = false;
(async () => {
try {
const { data } = await api.get('/api/schools/suggest', {
params: { query: q, limit: 10 },
withCredentials: true,
});
if (!cancelled) setSchoolSug(Array.isArray(data) ? data : []);
} catch (e) {
if (!cancelled) setSchoolSug([]);
}
return tuitionType === 'inState'
? parseFloat(match.TUITION5 || match.TUITION6 || '')
: parseFloat(match.TUITION7 || '');
};
})();
return () => { cancelled = true; };
}, [open, schoolSearch]);
const est = calc();
if (est && !tuitionManual) setTuition(String(est));
}, [schoolSearch, tuitionType, degree, cipData, icData, tuitionManual]);
/* ───────── PROGRAM SUGGEST (server; depends on school) ───────── */
useEffect(() => {
if (!open) return;
if (!selectedSchool?.name) { setProgramSug([]); return; }
const q = programSearch.trim();
if (q.length < 2) { setProgramSug([]); return; }
let cancelled = false;
(async () => {
try {
const { data } = await api.get('/api/programs/suggest', {
params: { school: selectedSchool.name, query: q, limit: 10 },
withCredentials: true,
});
if (!cancelled) setProgramSug(Array.isArray(data) ? data : []);
} catch (e) {
if (!cancelled) setProgramSug([]);
}
})();
return () => { cancelled = true; };
}, [open, selectedSchool?.name, programSearch]);
/* ───────── DEGREE TYPES (server; depends on school+program) ───────── */
useEffect(() => {
if (!open) return;
if (!selectedSchool?.name || !selectedProgram) { setDegreeTypes([]); return; }
let cancelled = false;
(async () => {
try {
const { data } = await api.get('/api/programs/types', {
params: { school: selectedSchool.name, program: selectedProgram },
withCredentials: true,
});
const types = Array.isArray(data?.types) ? data.types : [];
if (!cancelled) setDegreeTypes(types);
} catch (e) {
if (!cancelled) setDegreeTypes([]);
}
})();
return () => { cancelled = true; };
}, [open, selectedSchool?.name, selectedProgram]);
/* ───────── TUITION ESTIMATE (server) ───────── */
useEffect(() => {
if (!open) return;
if (!selectedSchool?.unitId) return;
if (!degree) return;
if (tuitionManual) return; // user overrode
(async () => {
try {
const { data } = await api.get('/api/tuition/estimate', {
params: {
unitId : String(selectedSchool.unitId),
programType: degree,
inState : tuitionType === 'inState' ? 1 : 0,
inDistrict: 0,
creditHoursPerYear: 0, // use school's full-time figure (server picks full when 0)
},
withCredentials: true,
});
if (data?.estimate) setTuition(String(data.estimate));
} catch (e) {
// keep existing value; user can enter manually
}
})();
},[open, selectedSchool?.unitId, degree, tuitionType, tuitionManual]);
/*
ESC --> close convenience
@ -200,7 +222,7 @@ const handleContinue = () => {
}
const stub = {
name : schoolSearch || 'Unknown School',
name : (selectedSchool?.name || schoolSearch || 'Unknown School'),
degreeType : degree || 'Unspecified',
programLength : 4,
tuition : parseFloat(tuition),
@ -247,23 +269,89 @@ const handleContinue = () => {
className="space-y-4"
onSubmit={e => { e.preventDefault(); handleContinue(); }}
>
{/* School name (optional) */}
<div>
{/* School name (server suggest) */}
<div className="relative">
<label className="text-sm font-medium">School name</label>
<input
type="text"
value={schoolSearch}
onChange={e => setSchoolSearch(e.target.value)}
list="school-suggestions"
onChange={e => {
setSchoolSearch(e.target.value);
setSelectedSchool({ name:'', unitId:null });
setSchoolSugOpen(true); setSchoolHi(-1);
// Clear program/degree when school changes
setProgramSearch(''); setSelectedProgram(''); setDegree(''); setDegreeTypes([]);
}}
onFocus={() => setSchoolSugOpen(true)}
onBlur={() => setTimeout(() => setSchoolSugOpen(false), 120)}
placeholder="Start typing…"
className="mt-1 w-full rounded border px-3 py-2 text-sm"
onBlur={e => setSchoolSearch(e.target.value.trim())}
onKeyDown={e => {
if (!schoolSugOpen || schoolSug.length === 0) return;
if (e.key === 'ArrowDown') { e.preventDefault(); setSchoolHi(h => Math.min(h + 1, schoolSug.length - 1)); }
else if (e.key === 'ArrowUp') { e.preventDefault(); setSchoolHi(h => Math.max(h - 1, 0)); }
else if (e.key === 'Enter' && schoolHi >= 0) {
e.preventDefault();
const opt = schoolSug[schoolHi]; setSchoolSearch(opt.name); setSelectedSchool(opt); setSchoolSugOpen(false);
}
}}
/>
<datalist id="school-suggestions">
{suggestions.map((s, i) => (
<option key={i} value={s} />
))}
</datalist>
{schoolSugOpen && schoolSug.length > 0 && (
<div className="absolute z-50 mt-1 w-full max-h-56 overflow-auto rounded border bg-white shadow">
{schoolSug.map((s, i) => (
<button
key={`${s.name}-${s.unitId}-${i}`}
type="button"
className={`block w-full text-left px-3 py-2 text-sm hover:bg-gray-50 ${schoolHi===i ? 'bg-gray-100' : ''}`}
onMouseEnter={() => setSchoolHi(i)}
onMouseDown={e => e.preventDefault()}
onClick={() => { setSchoolSearch(s.name); setSelectedSchool(s); setSchoolSugOpen(false); }}
>
{s.name}
</button>
))}
</div>
)}
</div>
{/* Program (server suggest, requires selected school) */}
<div className="relative">
<label className="text-sm font-medium">Program / major</label>
<input
type="text"
value={programSearch}
onChange={e => { setProgramSearch(e.target.value); setSelectedProgram(''); setProgramSugOpen(true); setProgramHi(-1); }}
onFocus={() => setProgramSugOpen(true)}
onBlur={() => setTimeout(() => setProgramSugOpen(false), 120)}
placeholder={selectedSchool?.name ? 'Start typing…' : 'Pick a school first'}
disabled={!selectedSchool?.name}
className="mt-1 w-full rounded border px-3 py-2 text-sm disabled:bg-gray-100"
onKeyDown={e => {
if (!programSugOpen || programSug.length === 0) return;
if (e.key === 'ArrowDown') { e.preventDefault(); setProgramHi(h => Math.min(h + 1, programSug.length - 1)); }
else if (e.key === 'ArrowUp') { e.preventDefault(); setProgramHi(h => Math.max(h - 1, 0)); }
else if (e.key === 'Enter' && programHi >= 0) {
e.preventDefault();
const opt = programSug[programHi]; setProgramSearch(opt.program); setSelectedProgram(opt.program); setProgramSugOpen(false);
}
}}
/>
{programSugOpen && programSug.length > 0 && (
<div className="absolute z-50 mt-1 w-full max-h-56 overflow-auto rounded border bg-white shadow">
{programSug.map((s, i) => (
<button
key={`${s.program}-${i}`}
type="button"
className={`block w-full text-left px-3 py-2 text-sm hover:bg-gray-50 ${programHi===i ? 'bg-gray-100' : ''}`}
onMouseEnter={() => setProgramHi(i)}
onMouseDown={e => e.preventDefault()}
onClick={() => { setProgramSearch(s.program); setSelectedProgram(s.program); setProgramSugOpen(false); }}
>
{s.program}
</button>
))}
</div>
)}
</div>
{/* Residency */}
@ -286,6 +374,7 @@ const handleContinue = () => {
value={degree}
onChange={e => setDegree(e.target.value)}
className="mt-1 w-full rounded border px-3 py-2 text-sm"
disabled={degreeTypes.length === 0 && !selectedProgram}
>
<option value="">Select</option>
{degreeMenu.map((d, i) => (

View File

@ -1,3 +1,4 @@
// src/components/PlanningLanding.jsx
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from './ui/button.js';
@ -6,46 +7,52 @@ function PlanningLanding() {
const navigate = useNavigate();
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
<h1 className="text-3xl font-bold mb-4 text-center">
Planning Your Career
</h1>
<p className="text-gray-600 mb-6 text-center">
Discover career options that match your interests, skills, and potential.
AptivaAI helps you find your ideal career path, provides insights into educational requirements,
expected salaries, job market trends, and more.
</p>
<div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-aptiva-gray to-white p-6">
<div className="max-w-2xl w-full bg-white shadow-xl rounded-xl p-8 md:p-10">
{/* Header */}
<div className="text-center mb-8">
<h1 className="text-3xl md:text-4xl font-bold text-aptiva mb-2">
Planning Your Career
</h1>
<p className="text-gray-600 max-w-xl mx-auto text-sm md:text-base leading-relaxed">
Discover career options that match your interests, skills, and
potential. AptivaAI helps you explore ideal paths, understand
educational requirements, compare salaries, and analyze job market
trendsall in one place.
</p>
</div>
<div className="grid grid-cols-1 gap-6">
<div>
<Button className="w-full" onClick={() => navigate('/interest-inventory')}>
Take Interest Inventory
</Button>
<p className="mt-2 text-sm text-gray-500">
Identify your interests and discover careers aligned with your strengths.
{/* Options grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Interest Inventory */}
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
<h2 className="text-lg font-semibold mb-2">Interest Inventory</h2>
<p className="text-sm text-gray-600 mb-4 flex-1">
Identify your interests and discover careers aligned with your
strengths.
</p>
<Button
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
onClick={() => navigate('/interest-inventory')}
>
Take Inventory
</Button>
</div>
<div>
<Button className="w-full" onClick={() => navigate('/career-explorer')}>
Explore Career Paths
</Button>
<p className="mt-2 text-sm text-gray-500">
Research detailed career profiles, job descriptions, salaries, and employment outlooks.
{/* Career Explorer */}
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
<h2 className="text-lg font-semibold mb-2">Career Explorer</h2>
<p className="text-sm text-gray-600 mb-4 flex-1">
Research career profiles, job descriptions, salaries, and
employment outlooks.
</p>
</div>
<div>
<Button className="w-full" onClick={() => navigate('/educational-programs')}>
Discover Educational Programs
<Button
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
onClick={() => navigate('/career-explorer')}
>
Explore Careers
</Button>
<p className="mt-2 text-sm text-gray-500">
Find the right educational programs, degrees, and certifications needed to pursue your chosen career.
</p>
</div>
</div>
</div>
</div>

View File

@ -1,27 +1,26 @@
// src/components/PreparingLanding.js
// src/components/PreparingLanding.js
import React, { useState, useCallback, useEffect, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { Button } from './ui/button.js';
import LoanRepaymentDrawer from './LoanRepaymentDrawer.js';
import { ProfileCtx } from '../App.js';
function PreparingLanding() {
const navigate = useNavigate();
const location = useLocation();
const { user } = useContext(ProfileCtx);
/* ─── Drawer visibility ─────────────────────────────── */
const [showLoan, setShowLoan] = useState(false);
/* ─── Stub-school state lives here; Drawer mutates it ─ */
const [schools, setSchools] = useState([]); // [] → quick-fill form
const [cipCodes] = useState([]); // you may hold these at page level
const [userZip] = useState('');
const [schools, setSchools] = useState([]); // [] → quick-fill form
const [cipCodes] = useState([]); // you may hold these at page level
const [userZip] = useState('');
const [loanResults, setLoanResults] = useState([]);
/* Esc -to-close convenience */
const escHandler = useCallback(e => {
const escHandler = useCallback((e) => {
if (e.key === 'Escape') setShowLoan(false);
}, []);
useEffect(() => {
@ -29,58 +28,83 @@ function PreparingLanding() {
return () => window.removeEventListener('keydown', escHandler);
}, [showLoan, escHandler]);
/* ─── Auto-open drawer when routed from nav ───────── */
useEffect(() => {
// support either query ?loan=1 or navigation state { openLoan: true }
try {
const qs = new URLSearchParams(location.search);
const viaQuery = qs.get('loan') === '1';
const viaState = location.state && location.state.openLoan === true;
if ((viaQuery || viaState) && !showLoan) {
setShowLoan(true);
}
} catch {}
}, [location.search, location.state, showLoan]);
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
<div className="max-w-3xl w-full bg-white shadow-lg rounded-lg p-8 space-y-6">
<div className="min-h-screen flex items-center justify-center bg-gradient-to-b from-aptiva-gray to-white p-6">
<div className="w-full max-w-3xl bg-white shadow-xl rounded-xl p-8 md:p-10 space-y-8">
{/* ───────────────── TITLE / INTRO ───────────────── */}
<h1 className="text-3xl font-bold text-center">
Preparing for Your (Next) Career
</h1>
<p className="text-gray-600 text-center">
Build the right skills and plan your education so you can confidently
enteror transition intoyour new career.
</p>
<div className="text-center">
<h1 className="text-3xl md:text-4xl font-bold text-aptiva mb-2">
Preparing for Your (Next) Career
</h1>
<p className="text-gray-600 max-w-2xl mx-auto text-sm md:text-base leading-relaxed">
Build the right skills and plan your education so you can confidently
enteror transition intoyour new career.
</p>
</div>
{/* ──────────────── 1) PATH CHOICE ──────────────── */}
<section className="space-y-4">
<h2 className="text-xl font-semibold">Which Path Fits You?</h2>
<p className="text-gray-700">
We can help you identify whether a&nbsp;
<strong>skills-based program</strong> (certifications, bootcamps) or a&nbsp;
<strong>formal education route</strong> (two- or four-year college)
is the best fit. Whichever path you choose, AptivaAI will help you map next stepsfrom applying to graduating.
We can help you identify whether a <strong>skills-based program</strong> (certifications, bootcamps) or a{' '}
<strong>formal education route</strong> (two- or four-year college) is the best fit. Whichever path you choose,
AptivaAI will help you map next stepsfrom applying to graduating.
</p>
<div className="flex flex-col sm:flex-row gap-4">
<Button onClick={() => navigate('/educational-programs')}>
Plan My Education Path
</Button>
<Button onClick={() => setShowLoan(true)}>
Cost of Education & Loan Repayment
</Button>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Education path card */}
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
<h3 className="text-lg font-semibold mb-2">Plan My Education Path</h3>
<p className="text-sm text-gray-600 mb-4 flex-1">
Compare degrees, certificates, and training paths for your target role.
</p>
<Button
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
onClick={() => navigate('/educational-programs')}
>
Plan Education
</Button>
</div>
{/* Loan/Cost card */}
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
<h3 className="text-lg font-semibold mb-2">Cost of Education &amp; Loan Repayment</h3>
<p className="text-sm text-gray-600 mb-4 flex-1">
Estimate total costs and monthly payments with realistic assumptions.
</p>
<Button
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
onClick={() => setShowLoan(true)}
>
Open Loan Tool
</Button>
</div>
</div>
</section>
{/* ──────────────── 2) LOAN BLURB ──────────────── */}
<section className="space-y-4">
<h2 className="text-xl font-semibold">Financing Your Future</h2>
<p className="text-gray-700">
Already have an idea of where you want to enroll? Compare costs,
estimate student-loan repayments, and map out work-study or part-time
opportunities. Our integrated <strong>LoanRepayment</strong> tool shows
realistic monthly payments so you can make confident choices.
</p>
</section>
{/* ──────────────── 3) INTEREST INVENTORY ──────────────── */}
{/* ──────────────── 2) STILL EXPLORING ──────────────── */}
<section className="space-y-3">
<h2 className="text-xl font-semibold">Still Exploring?</h2>
<p className="text-gray-700">
Want to revisit career possibilities? Retake our Interest Inventory to
see other matching paths.
Want to revisit career possibilities? Retake our Interest Inventory to see other matching paths.
</p>
<Button onClick={() => navigate('/interest-inventory')}>
<Button
className="bg-aptiva hover:bg-aptiva-dark text-white"
onClick={() => navigate('/interest-inventory')}
>
Retake Interest Inventory
</Button>
</section>
@ -90,7 +114,11 @@ function PreparingLanding() {
{showLoan && (
<LoanRepaymentDrawer
open={showLoan}
onClose={() => setShowLoan(false)}
onClose={() => {
setShowLoan(false);
// clear nav flag so it doesnt auto-reopen
navigate({ pathname: location.pathname }, { replace: true, state: {} });
}}
schools={schools}
setSchools={setSchools}
results={loanResults}

View File

@ -1,25 +1,39 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from './ui/button.js';
import React from "react";
import { useNavigate } from "react-router-dom";
import { Button } from "./ui/button.js";
function RetirementLanding() {
export default function RetirementLanding() {
const navigate = useNavigate();
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
<h1 className="text-3xl font-bold mb-4 text-center">
<div className="min-h-screen bg-gray-50 py-10 px-4">
<div className="max-w-3xl mx-auto bg-white rounded-2xl shadow p-6 md:p-8">
{/* Title + intro (centered, blue headline) */}
<h1 className="text-3xl font-bold text-blue-600 text-center">
Retirement Planning
</h1>
<p className="text-gray-600 mb-6 text-center">
Plan strategically and financially for retirement. AptivaAI provides you with clear financial projections, milestone tracking, and scenario analysis for a secure future.
<p className="text-center text-gray-600 mt-2">
Plan strategically and financially for retirement. Model scenarios,
understand trade-offs, and track the milestones that keep you on course.
</p>
<div className="grid grid-cols-1 gap-4">
<Button onClick={() => navigate('/retirement-planner')}>Compare different retirement scenarios and get AI help with planning</Button>
{/* Feature tile(s) inside main card */}
<div className="grid gap-6 md:gap-8 md:grid-cols-2 mt-6">
{/* Retirement Planner */}
<div className="rounded-xl border border-gray-200 p-5 md:col-span-2">
<h3 className="text-lg font-medium">Retirement Planner</h3>
<p className="text-gray-600 mt-1">
Compare scenarios, project savings and income, and get AI guidance to
fine-tune your plan.
</p>
<div className="mt-4">
<Button size="lg" onClick={() => navigate("/retirement-planner")}>
Open Retirement Planner
</Button>
</div>
</div>
</div>
</div>
</div>
);
}
export default RetirementLanding;

View File

@ -84,14 +84,17 @@ function SignIn({ setIsAuthenticated, setUser }) {
};
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-100 p-4">
<div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-aptiva-gray to-white p-4">
{showSessionExpiredMsg && (
<div className="mb-4 p-2 bg-red-100 border border-red-300 text-red-700 rounded">
Your session has expired. Please sign in again.
</div>
)}
<div className="w-full max-w-sm rounded-md bg-white p-6 shadow-md">
<h1 className="mb-6 text-center text-2xl font-semibold">Sign In</h1>
<div className="w-full max-w-sm rounded-xl bg-white p-6 shadow-lg border border-gray-100">
{/* Wordmark (text-only) */}
<div className="mb-1 text-center text-aptiva font-semibold tracking-tight">AptivaAI</div>
<h1 className="mb-1 text-center text-2xl font-semibold">Sign In</h1>
<p className="mb-6 text-center text-xs text-gray-500">Career guidance powered by data enhanced by AI</p>
{error && (
<p className="mb-4 rounded bg-red-50 p-2 text-sm text-red-600">
@ -99,24 +102,24 @@ function SignIn({ setIsAuthenticated, setUser }) {
</p>
)}
<form onSubmit={handleSignIn} className="flex flex-col space-y-4">
<form onSubmit={handleSignIn} className="flex flex-col space-y-3 md:space-y-4">
<input
type="text"
placeholder="Username"
ref={usernameRef}
className="w-full rounded border border-gray-300 p-2 focus:border-blue-500 focus:outline-none"
className="w-full rounded border border-gray-300 p-3 md:p-2 h-12 md:h-10 text-base md:text-sm focus:border-aptiva focus:outline-none"
autoComplete="username"
/>
<input
type="password"
placeholder="Password"
ref={passwordRef}
className="w-full rounded border border-gray-300 p-2 focus:border-blue-500 focus:outline-none"
className="w-full rounded border border-gray-300 p-3 md:p-2 h-12 md:h-10 text-base md:text-sm focus:border-aptiva focus:outline-none"
autoComplete="current-password"
/>
<button
type="submit"
className="mx-auto rounded bg-blue-600 px-6 py-2 text-center text-white transition-colors hover:bg-blue-700 focus:outline-none"
className="w-full md:w-auto md:mx-auto rounded bg-aptiva px-6 h-12 md:h-10 text-center text-white transition-colors hover:bg-aptiva-dark focus:outline-none"
>
Sign In
</button>
@ -125,7 +128,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
<div className="mt-4 space-y-2">
<p className="text-center text-sm text-gray-600">
Dont have an account?{' '}
<Link to="/signup" className="text-blue-600 hover:underline">
<Link to="/signup" className="text-aptiva hover:underline">
Sign Up
</Link>
</p>
@ -133,7 +136,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
<div className="text-center">
<Link
to="/forgot-password"
className="inline-block text-sm text-blue-600 hover:underline"
className="inline-block text-sm text-aptiva hover:underline"
>
Forgot your password?
</Link>

View File

@ -1,44 +1,89 @@
// SignInLanding.jsx
// src/components/SignInLanding.jsx
import React from 'react';
import { Link } from 'react-router-dom';
function SignInLanding({ user }) {
return (
<div className="max-w-2xl mx-auto p-4 bg-white shadow rounded">
<h2 className="text-2xl font-semibold mb-4">
Welcome to AptivaAI {user?.firstname}!
</h2>
<p className="mb-4">
At AptivaAI, we aim to arm you with as much information as possible to make informed career decisions. Todays workplace is changing faster than ever, driven largely by AIbut our goal is to use that same technology to empower job seekers, not replace them.
<div className="max-w-2xl md:max-w-3xl mx-auto p-4 bg-white shadow rounded">
{/* Soft brand header band */}
<div className="rounded-md bg-aptiva-gray/70 px-4 py-3 mb-4">
<div className="text-aptiva text-sm font-semibold tracking-tight">
AptivaAI
</div>
<h2 className="text-xl md:text-2xl font-semibold">
Welcome{user?.firstname ? `, ${user.firstname}` : ''}!
</h2>
<p className="text-xs md:text-sm text-gray-600">
Career guidance powered by data enhanced by AI
</p>
</div>
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 drivers seat of your career. Whether youre planning your first step, advancing your current role, or ready to pivot entirely, our platform keeps you in controlhelping you adapt, grow, and thrive on your own terms.
{/* Intro copy */}
<p className="mb-4 text-sm md:text-base leading-relaxed md:leading-7">
At AptivaAI, we aim to arm you with as much information as possible to
make informed career decisions. Todays workplace is changing faster
than ever, driven largely by AIbut our goal is to use that same
technology to empower job seekers, not replace them.
<br />
<br />
We blend data-backed insights with human-centered design, enhancednot
driven byAI. Giving you practical recommendations and real-world
context so you stay in the drivers seat of your career. Whether youre
planning your first step, advancing your current role, or ready to pivot
entirely, our platform keeps you in controlhelping you adapt, grow, and
thrive on your own terms.
</p>
<ul className="list-disc ml-6 mb-4">
<li><strong>Planning:</strong> 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.</li>
<li><strong>Preparing:</strong> Know what you want but just not how to get there? Gain education, skills, or certifications required to start or transition.</li>
<li><strong>Enhancing:</strong> 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.</li>
<li><strong>Retirement:</strong> On your happy path and want to make sure you're financially ready when the time comes? Prepare financially and strategically for retirement.</li>
</ul>
<p className="mb-4">
Where would you like to go next?
</p>
{/* Mobile: stacked full-width; Desktop: original inline buttons with spacing */}
<div className="flex flex-col gap-2 md:block md:space-x-2">
<Link to="/planning" className="w-full md:w-auto h-12 md:h-auto inline-flex items-center justify-center md:inline-block px-4 bg-blue-600 text-white rounded hover:bg-blue-700">
Go to Exploring
<p className="mb-2 md:mb-3">Where would you like to go next?</p>
{/* Four clickable tiles */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
<Link
to="/planning"
className="block rounded-lg border px-4 py-3 transition-colors bg-white hover:bg-aptiva hover:text-white focus:outline-none focus:ring-2 focus:ring-aptiva"
>
<div className="text-sm font-semibold">Planning</div>
<p className="text-sm">
Just starting out or exploring a better fit? See careers that match
your interests and skills.
</p>
</Link>
<Link to="/preparing" className="w-full md:w-auto h-12 md:h-auto inline-flex items-center justify-center md:inline-block px-4 bg-blue-600 text-white rounded hover:bg-blue-700">
Go to Preparing
<Link
to="/preparing"
className="block rounded-lg border px-4 py-3 transition-colors bg-white hover:bg-aptiva hover:text-white focus:outline-none focus:ring-2 focus:ring-aptiva"
>
<div className="text-sm font-semibold">Preparing</div>
<p className="text-sm">
Know your target role but not the path? Find education, skills, or
certifications to start or transition.
</p>
</Link>
<Link to="/enhancing" className="w-full md:w-auto h-12 md:h-auto inline-flex items-center justify-center md:inline-block px-4 bg-green-600 text-white rounded hover:bg-green-700">
Go to Enhancing
<Link
to="/enhancing"
className="block rounded-lg border px-4 py-3 transition-colors bg-white hover:bg-aptiva hover:text-white focus:outline-none focus:ring-2 focus:ring-aptiva"
>
<div className="text-sm font-semibold">Enhancing</div>
<p className="text-sm">
Already in-field? Chart your next steppromotions, role shifts, and
growth moves.
</p>
</Link>
<Link to="/retirement" className="w-full md:w-auto h-12 md:h-auto inline-flex items-center justify-center md:inline-block px-4 bg-green-600 text-white rounded hover:bg-green-700">
Go to Retirement
<Link
to="/retirement"
className="block rounded-lg border px-4 py-3 transition-colors bg-white hover:bg-aptiva hover:text-white focus:outline-none focus:ring-2 focus:ring-aptiva"
>
<div className="text-sm font-semibold">Retirement</div>
<p className="text-sm">
On your happy path? Make sure the finances line up when the time
comes.
</p>
</Link>
</div>
</div>
);
}