Branding/UI & Loan Repayment fixes
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
This commit is contained in:
parent
5a0c7efd25
commit
e25662aae4
@ -1 +1 @@
|
|||||||
77b19da169acfc9f13cfa14f2fb425fea6c03ef4-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b
|
e520c3d4f21d892f230efe5f06e338b842191dd6-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b
|
||||||
|
@ -182,6 +182,7 @@ services:
|
|||||||
networks: [default, aptiva-shared]
|
networks: [default, aptiva-shared]
|
||||||
environment:
|
environment:
|
||||||
GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY}
|
GOOGLE_MAPS_API_KEY: ${GOOGLE_MAPS_API_KEY}
|
||||||
|
ENV_NAME: ${ENV_NAME}
|
||||||
ports: ["80:80", "443:443"]
|
ports: ["80:80", "443:443"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
@ -442,6 +442,12 @@ const cancelLogout = () => {
|
|||||||
>
|
>
|
||||||
Educational Programs
|
Educational Programs
|
||||||
</Link>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -689,6 +695,7 @@ const cancelLogout = () => {
|
|||||||
Preparing — Overview
|
Preparing — Overview
|
||||||
</Link>
|
</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="/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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,64 +1,60 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Button } from "./ui/button.js";
|
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 }) {
|
export default function EnhancingLanding({ userProfile }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const socCode = userProfile?.socCode;
|
|
||||||
const stateName = userProfile?.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-8 px-4">
|
<div className="min-h-screen bg-gray-50 py-10 px-4">
|
||||||
<div className="max-w-4xl mx-auto space-y-10">
|
<div className="max-w-3xl mx-auto bg-white rounded-2xl shadow p-6 md:p-8">
|
||||||
|
{/* Title + intro (centered, blue headline) */}
|
||||||
{/* 📌 Current status */}
|
<h1 className="text-3xl font-bold text-blue-600 text-center">
|
||||||
<section className="bg-white shadow rounded-lg p-6">
|
Enhancing Your Career
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
</h1>
|
||||||
📌 Your Current Status & Next Steps
|
<p className="text-center text-gray-600 mt-2">
|
||||||
</h2>
|
Build momentum in your current role and prepare for your next step.
|
||||||
<p className="text-gray-600 mb-4">
|
Use Career Coach for guided milestones, and fine-tune your materials
|
||||||
Evaluate where you are in your career and discover upcoming milestones with our AI recommendations.
|
with the Resume Optimizer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<EconomicProjections socCode={socCode} stateName={stateName} />
|
{/* 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">
|
<div className="mt-4">
|
||||||
<Button onClick={() => navigate("/career-roadmap")}>
|
<Button size="lg" onClick={() => navigate("/career-roadmap")}>
|
||||||
Open Career Coach
|
Open Career Coach
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-500 mt-3">
|
{/* Resume Optimizer (separate tool) */}
|
||||||
All of these tools live inside the <strong>Career Coach</strong>. Open it any time using the button above.
|
<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>
|
</p>
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,8 @@ import { Button } from './ui/button.js';
|
|||||||
import { getNearbySchools } from '../utils/getNearbySchools.js';
|
import { getNearbySchools } from '../utils/getNearbySchools.js';
|
||||||
import UpsellSummary from './UpsellSummary.js';
|
import UpsellSummary from './UpsellSummary.js';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import api from '../auth/apiClient.js';
|
||||||
|
|
||||||
|
|
||||||
/* ───────────────────────── CONSTANTS ───────────────────────── */
|
/* ───────────────────────── CONSTANTS ───────────────────────── */
|
||||||
const DEGREE_OPTS = [
|
const DEGREE_OPTS = [
|
||||||
@ -32,9 +34,21 @@ export default function LoanRepaymentDrawer({
|
|||||||
}) {
|
}) {
|
||||||
/* Hooks must always run – return null later if !open */
|
/* Hooks must always run – return null later if !open */
|
||||||
/* ── Remote data for auto-suggest ─ */
|
/* ── Remote data for auto-suggest ─ */
|
||||||
const [cipData, setCipData] = useState([]);
|
|
||||||
const [schoolSearch, setSchoolSearch] = 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 ─ */
|
/* ── Simple form fields ─ */
|
||||||
const [degree, setDegree] = useState('');
|
const [degree, setDegree] = useState('');
|
||||||
@ -62,17 +76,9 @@ const getAnnualTuition = (schoolsArr, typed, isGrad, resid) =>
|
|||||||
pickField(schoolsArr?.[0] ?? {}, isGrad, resid) ||
|
pickField(schoolsArr?.[0] ?? {}, isGrad, resid) ||
|
||||||
(typed ? Number(typed) : 0);
|
(typed ? Number(typed) : 0);
|
||||||
|
|
||||||
|
// Degree menu: prefer backend-provided types (if any), else fall back
|
||||||
|
const degreeMenu = degreeTypes.length ? degreeTypes : DEGREE_OPTS;
|
||||||
|
|
||||||
/* ── memo’ed 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 isGrad = /(Master|Doctoral|First Professional|Graduate|Certificate)/i.test(degree);
|
||||||
const annualTuition= getAnnualTuition(schools, tuition, isGrad, tuitionType);
|
const annualTuition= getAnnualTuition(schools, tuition, isGrad, tuitionType);
|
||||||
|
|
||||||
@ -82,86 +88,102 @@ useEffect(() => {
|
|||||||
if (!open) return;
|
if (!open) return;
|
||||||
if (schools.length) return;
|
if (schools.length) return;
|
||||||
if (!cipCodes.length) return;
|
if (!cipCodes.length) return;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const seed = await getNearbySchools(cipCodes, userZip, userState);
|
const seed = await getNearbySchools(cipCodes, userZip, userState);
|
||||||
if (seed.length) setSchools(seed);
|
if (seed.length) setSchools(seed);
|
||||||
} catch (e) {
|
} catch (e) { console.warn('auto-seed schools failed:', e); }
|
||||||
console.warn('auto-seed schools failed:', e);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
}, [open, schools.length, cipCodes.join('-'), userZip, userState, setSchools]);
|
}, [open, schools.length, cipCodes.join('-'), userZip, userState, setSchools]);
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════════
|
/* ───────── SCHOOL SUGGEST (server) ───────── */
|
||||||
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 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(() => {
|
useEffect(() => {
|
||||||
if (!open || icData.length) return;
|
if (!open) return;
|
||||||
fetch('/ic2023_ay.csv')
|
const q = schoolSearch.trim();
|
||||||
.then(r => r.text())
|
if (q.length < 2) { setSchoolSug([]); return; }
|
||||||
.then(text => {
|
let cancelled = false;
|
||||||
const [header, ...rows] = text.split('\n').map(l => l.split(','));
|
(async () => {
|
||||||
return rows.map(row =>
|
try {
|
||||||
Object.fromEntries(row.map((v, i) => [header[i], v]))
|
const { data } = await api.get('/api/schools/suggest', {
|
||||||
);
|
params: { query: q, limit: 10 },
|
||||||
})
|
withCredentials: true,
|
||||||
.then(setIcData)
|
});
|
||||||
.catch(e => console.error('iPEDS load fail', e));
|
if (!cancelled) setSchoolSug(Array.isArray(data) ? data : []);
|
||||||
}, [open, icData.length]);
|
} catch (e) {
|
||||||
|
if (!cancelled) setSchoolSug([]);
|
||||||
/* ───────── 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 || '');
|
|
||||||
}
|
}
|
||||||
return tuitionType === 'inState'
|
})();
|
||||||
? parseFloat(match.TUITION5 || match.TUITION6 || '')
|
return () => { cancelled = true; };
|
||||||
: parseFloat(match.TUITION7 || '');
|
}, [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
|
ESC --> close convenience
|
||||||
@ -200,7 +222,7 @@ const handleContinue = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stub = {
|
const stub = {
|
||||||
name : schoolSearch || 'Unknown School',
|
name : (selectedSchool?.name || schoolSearch || 'Unknown School'),
|
||||||
degreeType : degree || 'Unspecified',
|
degreeType : degree || 'Unspecified',
|
||||||
programLength : 4,
|
programLength : 4,
|
||||||
tuition : parseFloat(tuition),
|
tuition : parseFloat(tuition),
|
||||||
@ -247,23 +269,89 @@ const handleContinue = () => {
|
|||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
onSubmit={e => { e.preventDefault(); handleContinue(); }}
|
onSubmit={e => { e.preventDefault(); handleContinue(); }}
|
||||||
>
|
>
|
||||||
{/* School name (optional) */}
|
{/* School name (server suggest) */}
|
||||||
<div>
|
<div className="relative">
|
||||||
<label className="text-sm font-medium">School name</label>
|
<label className="text-sm font-medium">School name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={schoolSearch}
|
value={schoolSearch}
|
||||||
onChange={e => setSchoolSearch(e.target.value)}
|
onChange={e => {
|
||||||
list="school-suggestions"
|
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…"
|
placeholder="Start typing…"
|
||||||
className="mt-1 w-full rounded border px-3 py-2 text-sm"
|
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">
|
{schoolSugOpen && schoolSug.length > 0 && (
|
||||||
{suggestions.map((s, i) => (
|
<div className="absolute z-50 mt-1 w-full max-h-56 overflow-auto rounded border bg-white shadow">
|
||||||
<option key={i} value={s} />
|
{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>
|
||||||
))}
|
))}
|
||||||
</datalist>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Residency */}
|
{/* Residency */}
|
||||||
@ -286,6 +374,7 @@ const handleContinue = () => {
|
|||||||
value={degree}
|
value={degree}
|
||||||
onChange={e => setDegree(e.target.value)}
|
onChange={e => setDegree(e.target.value)}
|
||||||
className="mt-1 w-full rounded border px-3 py-2 text-sm"
|
className="mt-1 w-full rounded border px-3 py-2 text-sm"
|
||||||
|
disabled={degreeTypes.length === 0 && !selectedProgram}
|
||||||
>
|
>
|
||||||
<option value="">Select…</option>
|
<option value="">Select…</option>
|
||||||
{degreeMenu.map((d, i) => (
|
{degreeMenu.map((d, i) => (
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// src/components/PlanningLanding.jsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Button } from './ui/button.js';
|
import { Button } from './ui/button.js';
|
||||||
@ -6,46 +7,52 @@ function PlanningLanding() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
<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-lg rounded-lg p-8">
|
<div className="max-w-2xl w-full bg-white shadow-xl rounded-xl p-8 md:p-10">
|
||||||
<h1 className="text-3xl font-bold mb-4 text-center">
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-aptiva mb-2">
|
||||||
Planning Your Career
|
Planning Your Career
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 mb-6 text-center">
|
<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.
|
Discover career options that match your interests, skills, and
|
||||||
AptivaAI helps you find your ideal career path, provides insights into educational requirements,
|
potential. AptivaAI helps you explore ideal paths, understand
|
||||||
expected salaries, job market trends, and more.
|
educational requirements, compare salaries, and analyze job market
|
||||||
</p>
|
trends—all in one place.
|
||||||
|
|
||||||
<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.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{/* Options grid */}
|
||||||
<Button className="w-full" onClick={() => navigate('/career-explorer')}>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
Explore Career Paths
|
{/* Interest Inventory */}
|
||||||
</Button>
|
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
|
||||||
<p className="mt-2 text-sm text-gray-500">
|
<h2 className="text-lg font-semibold mb-2">Interest Inventory</h2>
|
||||||
Research detailed career profiles, job descriptions, salaries, and employment outlooks.
|
<p className="text-sm text-gray-600 mb-4 flex-1">
|
||||||
|
Identify your interests and discover careers aligned with your
|
||||||
|
strengths.
|
||||||
</p>
|
</p>
|
||||||
|
<Button
|
||||||
|
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
|
||||||
|
onClick={() => navigate('/interest-inventory')}
|
||||||
|
>
|
||||||
|
Take Inventory
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{/* Career Explorer */}
|
||||||
<Button className="w-full" onClick={() => navigate('/educational-programs')}>
|
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
|
||||||
Discover Educational Programs
|
<h2 className="text-lg font-semibold mb-2">Career Explorer</h2>
|
||||||
</Button>
|
<p className="text-sm text-gray-600 mb-4 flex-1">
|
||||||
<p className="mt-2 text-sm text-gray-500">
|
Research career profiles, job descriptions, salaries, and
|
||||||
Find the right educational programs, degrees, and certifications needed to pursue your chosen career.
|
employment outlooks.
|
||||||
</p>
|
</p>
|
||||||
|
<Button
|
||||||
|
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
|
||||||
|
onClick={() => navigate('/career-explorer')}
|
||||||
|
>
|
||||||
|
Explore Careers
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
// src/components/PreparingLanding.js
|
// src/components/PreparingLanding.js
|
||||||
import React, { useState, useCallback, useEffect, useContext } from 'react';
|
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 { Button } from './ui/button.js';
|
||||||
import LoanRepaymentDrawer from './LoanRepaymentDrawer.js';
|
import LoanRepaymentDrawer from './LoanRepaymentDrawer.js';
|
||||||
import { ProfileCtx } from '../App.js';
|
import { ProfileCtx } from '../App.js';
|
||||||
|
|
||||||
function PreparingLanding() {
|
function PreparingLanding() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const { user } = useContext(ProfileCtx);
|
const { user } = useContext(ProfileCtx);
|
||||||
|
|
||||||
/* ─── Drawer visibility ─────────────────────────────── */
|
/* ─── Drawer visibility ─────────────────────────────── */
|
||||||
@ -19,9 +19,8 @@ function PreparingLanding() {
|
|||||||
const [userZip] = useState('');
|
const [userZip] = useState('');
|
||||||
const [loanResults, setLoanResults] = useState([]);
|
const [loanResults, setLoanResults] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
/* Esc -to-close convenience */
|
/* Esc -to-close convenience */
|
||||||
const escHandler = useCallback(e => {
|
const escHandler = useCallback((e) => {
|
||||||
if (e.key === 'Escape') setShowLoan(false);
|
if (e.key === 'Escape') setShowLoan(false);
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -29,58 +28,83 @@ function PreparingLanding() {
|
|||||||
return () => window.removeEventListener('keydown', escHandler);
|
return () => window.removeEventListener('keydown', escHandler);
|
||||||
}, [showLoan, 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 (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
<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-3xl w-full bg-white shadow-lg rounded-lg p-8 space-y-6">
|
<div className="w-full max-w-3xl bg-white shadow-xl rounded-xl p-8 md:p-10 space-y-8">
|
||||||
{/* ───────────────── TITLE / INTRO ───────────────── */}
|
{/* ───────────────── TITLE / INTRO ───────────────── */}
|
||||||
<h1 className="text-3xl font-bold text-center">
|
<div className="text-center">
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-aptiva mb-2">
|
||||||
Preparing for Your (Next) Career
|
Preparing for Your (Next) Career
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 text-center">
|
<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
|
Build the right skills and plan your education so you can confidently
|
||||||
enter—or transition into—your new career.
|
enter—or transition into—your new career.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* ──────────────── 1) PATH CHOICE ──────────────── */}
|
{/* ──────────────── 1) PATH CHOICE ──────────────── */}
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<h2 className="text-xl font-semibold">Which Path Fits You?</h2>
|
<h2 className="text-xl font-semibold">Which Path Fits You?</h2>
|
||||||
<p className="text-gray-700">
|
<p className="text-gray-700">
|
||||||
We can help you identify whether a
|
We can help you identify whether a <strong>skills-based program</strong> (certifications, bootcamps) or 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,
|
||||||
<strong>formal education route</strong> (two- or four-year college)
|
AptivaAI will help you map next steps—from applying to graduating.
|
||||||
is the best fit. Whichever path you choose, AptivaAI will help you map next steps—from applying to graduating.
|
|
||||||
|
|
||||||
</p>
|
</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)}>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
Cost of Education & Loan Repayment
|
{/* 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>
|
</Button>
|
||||||
</div>
|
</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 & 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>
|
</section>
|
||||||
|
|
||||||
{/* ──────────────── 2) LOAN BLURB ──────────────── */}
|
{/* ──────────────── 2) STILL EXPLORING ──────────────── */}
|
||||||
<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 ──────────────── */}
|
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<h2 className="text-xl font-semibold">Still Exploring?</h2>
|
<h2 className="text-xl font-semibold">Still Exploring?</h2>
|
||||||
<p className="text-gray-700">
|
<p className="text-gray-700">
|
||||||
Want to revisit career possibilities? Retake our Interest Inventory to
|
Want to revisit career possibilities? Retake our Interest Inventory to see other matching paths.
|
||||||
see other matching paths.
|
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => navigate('/interest-inventory')}>
|
<Button
|
||||||
|
className="bg-aptiva hover:bg-aptiva-dark text-white"
|
||||||
|
onClick={() => navigate('/interest-inventory')}
|
||||||
|
>
|
||||||
Retake Interest Inventory
|
Retake Interest Inventory
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
@ -90,7 +114,11 @@ function PreparingLanding() {
|
|||||||
{showLoan && (
|
{showLoan && (
|
||||||
<LoanRepaymentDrawer
|
<LoanRepaymentDrawer
|
||||||
open={showLoan}
|
open={showLoan}
|
||||||
onClose={() => setShowLoan(false)}
|
onClose={() => {
|
||||||
|
setShowLoan(false);
|
||||||
|
// clear nav flag so it doesn’t auto-reopen
|
||||||
|
navigate({ pathname: location.pathname }, { replace: true, state: {} });
|
||||||
|
}}
|
||||||
schools={schools}
|
schools={schools}
|
||||||
setSchools={setSchools}
|
setSchools={setSchools}
|
||||||
results={loanResults}
|
results={loanResults}
|
||||||
|
@ -1,25 +1,39 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Button } from './ui/button.js';
|
import { Button } from "./ui/button.js";
|
||||||
|
|
||||||
function RetirementLanding() {
|
export default function RetirementLanding() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 p-6">
|
<div className="min-h-screen bg-gray-50 py-10 px-4">
|
||||||
<div className="max-w-2xl w-full bg-white shadow-lg rounded-lg p-8">
|
<div className="max-w-3xl mx-auto bg-white rounded-2xl shadow p-6 md:p-8">
|
||||||
<h1 className="text-3xl font-bold mb-4 text-center">
|
{/* Title + intro (centered, blue headline) */}
|
||||||
|
<h1 className="text-3xl font-bold text-blue-600 text-center">
|
||||||
Retirement Planning
|
Retirement Planning
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 mb-6 text-center">
|
<p className="text-center text-gray-600 mt-2">
|
||||||
Plan strategically and financially for retirement. AptivaAI provides you with clear financial projections, milestone tracking, and scenario analysis for a secure future.
|
Plan strategically and financially for retirement. Model scenarios,
|
||||||
|
understand trade-offs, and track the milestones that keep you on course.
|
||||||
</p>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RetirementLanding;
|
|
||||||
|
@ -84,14 +84,17 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 && (
|
{showSessionExpiredMsg && (
|
||||||
<div className="mb-4 p-2 bg-red-100 border border-red-300 text-red-700 rounded">
|
<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.
|
Your session has expired. Please sign in again.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="w-full max-w-sm rounded-md bg-white p-6 shadow-md">
|
<div className="w-full max-w-sm rounded-xl bg-white p-6 shadow-lg border border-gray-100">
|
||||||
<h1 className="mb-6 text-center text-2xl font-semibold">Sign In</h1>
|
{/* 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 && (
|
{error && (
|
||||||
<p className="mb-4 rounded bg-red-50 p-2 text-sm text-red-600">
|
<p className="mb-4 rounded bg-red-50 p-2 text-sm text-red-600">
|
||||||
@ -99,24 +102,24 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
|||||||
</p>
|
</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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
ref={usernameRef}
|
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"
|
autoComplete="username"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
ref={passwordRef}
|
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"
|
autoComplete="current-password"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
@ -125,7 +128,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
|||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4 space-y-2">
|
||||||
<p className="text-center text-sm text-gray-600">
|
<p className="text-center text-sm text-gray-600">
|
||||||
Don’t have an account?{' '}
|
Don’t have an account?{' '}
|
||||||
<Link to="/signup" className="text-blue-600 hover:underline">
|
<Link to="/signup" className="text-aptiva hover:underline">
|
||||||
Sign Up
|
Sign Up
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
@ -133,7 +136,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
to="/forgot-password"
|
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?
|
Forgot your password?
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -1,44 +1,89 @@
|
|||||||
// SignInLanding.jsx
|
// src/components/SignInLanding.jsx
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
function SignInLanding({ user }) {
|
function SignInLanding({ user }) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto p-4 bg-white shadow rounded">
|
<div className="max-w-2xl md:max-w-3xl mx-auto p-4 bg-white shadow rounded">
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
{/* Soft brand header band */}
|
||||||
Welcome to AptivaAI {user?.firstname}!
|
<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>
|
</h2>
|
||||||
<p className="mb-4">
|
<p className="text-xs md:text-sm text-gray-600">
|
||||||
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.
|
Career guidance powered by data — enhanced by AI
|
||||||
|
|
||||||
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.
|
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc ml-6 mb-4">
|
</div>
|
||||||
|
|
||||||
<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>
|
{/* Intro copy */}
|
||||||
<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>
|
<p className="mb-4 text-sm md:text-base leading-relaxed md:leading-7">
|
||||||
<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>
|
At AptivaAI, we aim to arm you with as much information as possible to
|
||||||
<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>
|
make informed career decisions. Today’s workplace is changing faster
|
||||||
</ul>
|
than ever, driven largely by AI—but our goal is to use that same
|
||||||
<p className="mb-4">
|
technology to empower job seekers, not replace them.
|
||||||
Where would you like to go next?
|
<br />
|
||||||
|
<br />
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<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>
|
</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
|
|
||||||
</Link>
|
</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>
|
||||||
<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 step—promotions, role shifts, and
|
||||||
|
growth moves.
|
||||||
|
</p>
|
||||||
</Link>
|
</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>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user