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) */}
|
||||||
|
<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 */}
|
{/* Feature tiles (inside the main card) */}
|
||||||
<section className="bg-white shadow rounded-lg p-6">
|
<div className="grid gap-6 md:gap-8 md:grid-cols-2 mt-6">
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
{/* Career Coach */}
|
||||||
📌 Your Current Status & Next Steps
|
<div className="rounded-xl border border-gray-200 p-5">
|
||||||
</h2>
|
<h3 className="text-lg font-medium">Career Coach</h3>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 mt-1">
|
||||||
Evaluate where you are in your career and discover upcoming milestones with our AI recommendations.
|
Guided milestones for networking, interview prep, and job search.
|
||||||
</p>
|
</p>
|
||||||
|
<div className="mt-4">
|
||||||
<EconomicProjections socCode={socCode} stateName={stateName} />
|
<Button size="lg" onClick={() => navigate("/career-roadmap")}>
|
||||||
|
Open Career Coach
|
||||||
<div className="mt-4">
|
</Button>
|
||||||
<Button onClick={() => navigate("/career-roadmap")}>
|
</div>
|
||||||
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" />
|
|
||||||
</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">
|
||||||
</p>
|
<h3 className="text-lg font-medium">Resume Optimizer</h3>
|
||||||
</section>
|
<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>
|
||||||
</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,106 +76,114 @@ 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);
|
||||||
|
|
||||||
const showUpsell = user && !user.is_premium && !user.is_pro_premium;
|
const showUpsell = user && !user.is_premium && !user.is_pro_premium;
|
||||||
|
|
||||||
useEffect(() => {
|
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(() => {
|
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
if (!schoolSearch.trim() || !icData.length) return;
|
if (!open) return;
|
||||||
const rec = cipData.find(r => r.INSTNM.toLowerCase() === schoolSearch.toLowerCase());
|
const q = schoolSearch.trim();
|
||||||
if (!rec) return;
|
if (q.length < 2) { setSchoolSug([]); return; }
|
||||||
const match = icData.find(r => r.UNITID === rec.UNITID);
|
let cancelled = false;
|
||||||
if (!match) return;
|
(async () => {
|
||||||
|
try {
|
||||||
const calc = () => {
|
const { data } = await api.get('/api/schools/suggest', {
|
||||||
const grad = /(Master|Doctoral|First Professional|Graduate|Certificate)/i.test(degree);
|
params: { query: q, limit: 10 },
|
||||||
if (!grad) {
|
withCredentials: true,
|
||||||
return tuitionType === 'inState'
|
});
|
||||||
? parseFloat(match.TUITION1 || match.TUITION2 || '')
|
if (!cancelled) setSchoolSug(Array.isArray(data) ? data : []);
|
||||||
: parseFloat(match.TUITION3 || '');
|
} catch (e) {
|
||||||
|
if (!cancelled) setSchoolSug([]);
|
||||||
}
|
}
|
||||||
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
|
||||||
</datalist>
|
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>
|
</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 */}
|
||||||
Planning Your Career
|
<div className="text-center mb-8">
|
||||||
</h1>
|
<h1 className="text-3xl md:text-4xl font-bold text-aptiva mb-2">
|
||||||
<p className="text-gray-600 mb-6 text-center">
|
Planning Your Career
|
||||||
Discover career options that match your interests, skills, and potential.
|
</h1>
|
||||||
AptivaAI helps you find your ideal career path, provides insights into educational requirements,
|
<p className="text-gray-600 max-w-xl mx-auto text-sm md:text-base leading-relaxed">
|
||||||
expected salaries, job market trends, and more.
|
Discover career options that match your interests, skills, and
|
||||||
</p>
|
potential. AptivaAI helps you explore ideal paths, understand
|
||||||
|
educational requirements, compare salaries, and analyze job market
|
||||||
|
trends—all in one place.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6">
|
{/* Options grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
{/* Interest Inventory */}
|
||||||
<Button className="w-full" onClick={() => navigate('/interest-inventory')}>
|
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
|
||||||
Take Interest Inventory
|
<h2 className="text-lg font-semibold mb-2">Interest Inventory</h2>
|
||||||
</Button>
|
<p className="text-sm text-gray-600 mb-4 flex-1">
|
||||||
<p className="mt-2 text-sm text-gray-500">
|
Identify your interests and discover careers aligned with your
|
||||||
Identify your interests and discover careers aligned with your strengths.
|
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('/career-explorer')}>
|
<div className="flex flex-col border rounded-lg p-5 hover:shadow-md transition-colors">
|
||||||
Explore Career Paths
|
<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
|
||||||
Research detailed career profiles, job descriptions, salaries, and employment outlooks.
|
employment outlooks.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<Button
|
||||||
|
className="w-full mt-auto bg-aptiva hover:bg-aptiva-dark text-white"
|
||||||
<div>
|
onClick={() => navigate('/career-explorer')}
|
||||||
<Button className="w-full" onClick={() => navigate('/educational-programs')}>
|
>
|
||||||
Discover Educational Programs
|
Explore Careers
|
||||||
</Button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,27 +1,26 @@
|
|||||||
// 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 ─────────────────────────────── */
|
||||||
const [showLoan, setShowLoan] = useState(false);
|
const [showLoan, setShowLoan] = useState(false);
|
||||||
|
|
||||||
/* ─── Stub-school state lives here; Drawer mutates it ─ */
|
/* ─── Stub-school state lives here; Drawer mutates it ─ */
|
||||||
const [schools, setSchools] = useState([]); // [] → quick-fill form
|
const [schools, setSchools] = useState([]); // [] → quick-fill form
|
||||||
const [cipCodes] = useState([]); // you may hold these at page level
|
const [cipCodes] = useState([]); // you may hold these at page level
|
||||||
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">
|
||||||
Preparing for Your (Next) Career
|
<h1 className="text-3xl md:text-4xl font-bold text-aptiva mb-2">
|
||||||
</h1>
|
Preparing for Your (Next) Career
|
||||||
<p className="text-gray-600 text-center">
|
</h1>
|
||||||
Build the right skills and plan your education so you can confidently
|
<p className="text-gray-600 max-w-2xl mx-auto text-sm md:text-base leading-relaxed">
|
||||||
enter—or transition into—your new career.
|
Build the right skills and plan your education so you can confidently
|
||||||
</p>
|
enter—or transition into—your new career.
|
||||||
|
</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 */}
|
||||||
</Button>
|
<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 & 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>
|
</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">
|
||||||
</h2>
|
<div className="text-aptiva text-sm font-semibold tracking-tight">
|
||||||
<p className="mb-4">
|
AptivaAI
|
||||||
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.
|
</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 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.
|
{/* 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. 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.
|
||||||
|
<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>
|
||||||
<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>
|
<p className="mb-2 md:mb-3">Where would you like to go next?</p>
|
||||||
<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>
|
{/* Four clickable tiles */}
|
||||||
<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>
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
|
||||||
</ul>
|
<Link
|
||||||
<p className="mb-4">
|
to="/planning"
|
||||||
Where would you like to go next?
|
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"
|
||||||
</p>
|
>
|
||||||
{/* Mobile: stacked full-width; Desktop: original inline buttons with spacing */}
|
<div className="text-sm font-semibold">Planning</div>
|
||||||
<div className="flex flex-col gap-2 md:block md:space-x-2">
|
<p className="text-sm">
|
||||||
<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">
|
Just starting out or exploring a better fit? See careers that match
|
||||||
Go to Exploring
|
your interests and skills.
|
||||||
|
</p>
|
||||||
</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