Added CareerProfileForm and CollegeProfileForm, changed FinancialProfileForm for consistency, RetirementLanding button removal for FinancialProfileForm. Altered CareerOnboarding and ReviewPage

This commit is contained in:
Josh 2025-07-22 18:07:06 +00:00
parent 976ca69bfa
commit 7d8e9c5f61
12 changed files with 1157 additions and 233 deletions

View File

@ -193,8 +193,7 @@ app.get('/api/premium/career-profile/latest', authenticatePremiumUser, async (re
const sql = `
SELECT
*,
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date,
DATE_FORMAT(projected_end_date, '%Y-%m-%d') AS projected_end_date
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date
FROM career_profiles
WHERE user_id = ?
ORDER BY start_date DESC
@ -214,8 +213,7 @@ app.get('/api/premium/career-profile/all', authenticatePremiumUser, async (req,
const sql = `
SELECT
*,
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date,
DATE_FORMAT(projected_end_date, '%Y-%m-%d') AS projected_end_date
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date
FROM career_profiles
WHERE user_id = ?
ORDER BY start_date ASC
@ -235,8 +233,7 @@ app.get('/api/premium/career-profile/:careerProfileId', authenticatePremiumUser,
const sql = `
SELECT
*,
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date,
DATE_FORMAT(projected_end_date, '%Y-%m-%d') AS projected_end_date
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date
FROM career_profiles
WHERE id = ?
AND user_id = ?
@ -262,7 +259,6 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
career_name,
status,
start_date,
projected_end_date,
college_enrollment_status,
currently_working,
career_goals,
@ -295,7 +291,6 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
career_name,
status,
start_date,
projected_end_date,
college_enrollment_status,
currently_working,
career_goals,
@ -309,11 +304,10 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
planned_surplus_retirement_pct,
planned_additional_income
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
status = VALUES(status),
start_date = VALUES(start_date),
projected_end_date = VALUES(projected_end_date),
college_enrollment_status = VALUES(college_enrollment_status),
currently_working = VALUES(currently_working),
career_goals = VALUES(career_goals),
@ -336,7 +330,6 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
career_name,
status || 'planned',
start_date || null,
projected_end_date || null,
college_enrollment_status || null,
currently_working || null,
career_goals || null,
@ -2453,29 +2446,32 @@ app.delete('/api/premium/milestones/:milestoneId', authenticatePremiumUser, asyn
------------------------------------------------------------------ */
// GET /api/premium/financial-profile
app.get('/api/premium/financial-profile', auth, (req, res) => {
const uid = req.userId;
db.query('SELECT * FROM financial_profile WHERE user_id=?', [uid],
(err, rows) => {
if (err) return res.status(500).json({ error:'DB error' });
app.get('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => {
try {
const [rows] = await pool.query(
'SELECT * FROM financial_profiles WHERE user_id=? LIMIT 1',
[req.id]
);
if (!rows.length) {
// ←———— send a benign default instead of 404
return res.json({
current_salary: 0,
additional_income: 0,
monthly_expenses: 0,
monthly_debt_payments: 0,
retirement_savings: 0,
emergency_fund: 0,
retirement_contribution: 0,
emergency_contribution: 0,
extra_cash_emergency_pct: 50,
extra_cash_retirement_pct: 50
});
}
res.json(rows[0]);
});
if (!rows.length) {
return res.json({
current_salary: 0,
additional_income: 0,
monthly_expenses: 0,
monthly_debt_payments: 0,
retirement_savings: 0,
emergency_fund: 0,
retirement_contribution: 0,
emergency_contribution: 0,
extra_cash_emergency_pct: 50,
extra_cash_retirement_pct: 50
});
}
res.json(rows[0]);
} catch (err) {
console.error('financialprofile GET error:', err);
res.status(500).json({ error: 'DB error' });
}
});
app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req, res) => {
@ -2725,6 +2721,21 @@ app.get('/api/premium/college-profile', authenticatePremiumUser, async (req, res
}
});
// GET every college profile for the loggedin user
app.get('/api/premium/college-profile/all', authenticatePremiumUser, async (req,res)=>{
const sql = `
SELECT cp.*,
DATE_FORMAT(cp.created_at,'%Y-%m-%d') AS created_at,
IFNULL(cpr.scenario_title, cpr.career_name) AS career_title
FROM college_profiles cp
JOIN career_profiles cpr ON cpr.id = cp.career_profile_id
WHERE cp.user_id = ?
ORDER BY cp.created_at DESC
`;
const [rows] = await pool.query(sql,[req.id]);
res.json({ collegeProfiles: rows });
});
/* ------------------------------------------------------------------
AI-SUGGESTED MILESTONES
------------------------------------------------------------------ */

View File

@ -25,6 +25,10 @@ import InterestInventory from './components/InterestInventory.js';
import Dashboard from './components/Dashboard.js';
import UserProfile from './components/UserProfile.js';
import FinancialProfileForm from './components/FinancialProfileForm.js';
import CareerProfileList from './components/CareerProfileList.js';
import CareerProfileForm from './components/CareerProfileForm.js';
import CollegeProfileList from './components/CollegeProfileList.js';
import CollegeProfileForm from './components/CollegeProfileForm.js';
import CareerRoadmap from './components/CareerRoadmap.js';
import Paywall from './components/Paywall.js';
import OnboardingContainer from './components/PremiumOnboarding/OnboardingContainer.js';
@ -239,7 +243,7 @@ const uiToolHandlers = useMemo(() => {
{/* Header */}
<header className="flex items-center justify-between border-b bg-white px-6 py-4 shadow-sm relative">
<h1 className="text-lg font-semibold">
AptivaAI - Career Guidance Platform (beta)
AptivaAI - Career Guidance Platform
</h1>
{isAuthenticated && (
@ -360,7 +364,7 @@ const uiToolHandlers = useMemo(() => {
)}
onClick={() => navigate('/retirement')}
>
Retirement Planning
Retirement Planning (beta)
{!canAccessPremium && (
<span className="text-xs ml-1 text-gray-600">
(Premium)
@ -406,20 +410,32 @@ const uiToolHandlers = useMemo(() => {
</Link>
{canAccessPremium ? (
/* Premium users go straight to the wizard */
<Link
to="/premium-onboarding"
<Link
to="/profile/careers"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
Premium Onboarding
Career Profiles
</Link>
) : (
/* Free users are nudged to upgrade */
<span
className="block px-4 py-2 text-sm text-gray-400 cursor-not-allowed"
>
Career Profiles (Premium)
</span>
)}
{/* College Profiles (go straight to list) */}
{canAccessPremium ? (
<Link
to="/paywall"
to="/profile/college"
className="block px-4 py-2 hover:bg-gray-100 text-sm text-gray-700"
>
College Planning&nbsp;<span className="text-xs">(Premium)</span>
College Profiles
</Link>
) : (
<span className="block px-4 py-2 text-sm text-gray-400 cursor-not-allowed">
College Profiles (Premium)
</span>
)}
</div>
</div>
@ -533,6 +549,11 @@ const uiToolHandlers = useMemo(() => {
</PremiumRoute>
}
/>
<Route path="/profile/careers" element={<CareerProfileList />} />
<Route path="/profile/careers/:id/edit" element={<CareerProfileForm />} />
<Route path="/profile/college/" element={<CollegeProfileList />} />
<Route path="/profile/college/:careerId/:id?" element={<CollegeProfileForm />} />
<Route
path="/financial-profile"
element={

View File

@ -0,0 +1,232 @@
// CareerProfileForm.js
import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import authFetch from '../utils/authFetch.js';
import CareerSearch from './CareerSearch.js'; // ← same component as onboarding
export default function CareerProfileForm() {
const { id } = useParams(); // "new" or an existing uuid
const nav = useNavigate();
/* ---------- 1. local state ---------- */
const [form, setForm] = useState({
scenario_title : '',
career_name : '',
soc_code : '',
status : 'current',
start_date : '',
retirement_start_date : '',
college_enrollment_status : '',
career_goals : '',
desired_retirement_income_monthly : ''
});
const [careerLocked, setCareerLocked] = useState(id !== 'new'); // lock unless new
/* ---------- 2. helpers ---------- */
const handleChange = e =>
setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));
const handleCareerSelected = obj => {
// obj = { title, soc_code, … }
setForm(prev => ({
...prev,
career_name : obj.title,
soc_code : obj.soc_code
}));
setCareerLocked(true);
};
const unlockCareer = () => {
// allow user to repick
setCareerLocked(false);
setForm(prev => ({ ...prev, career_name: '', soc_code: '' }));
};
/* ---------- 3. load an existing row (edit mode) ---------- */
useEffect(() => {
if (id === 'new') return;
(async () => {
const res = await authFetch(`/api/premium/career-profile/${id}`);
if (!res.ok) return;
const d = await res.json();
setForm(prev => ({
...prev,
scenario_title : d.scenario_title ?? '',
career_name : d.career_name ?? '',
soc_code : d.soc_code ?? '',
status : d.status ?? 'current',
start_date : d.start_date ?? '',
retirement_start_date : d.retirement_start_date ?? '',
college_enrollment_status : d.college_enrollment_status ?? '',
career_goals : d.career_goals ?? '',
desired_retirement_income_monthly :
d.desired_retirement_income_monthly ?? ''
}));
})();
}, [id]);
/* ---------- 4. save ---------- */
async function save() {
if (!form.soc_code) {
alert('Please pick a valid career from the list first.');
return;
}
try {
const res = await authFetch('/api/premium/career-profile', {
method : 'POST',
headers : { 'Content-Type': 'application/json' },
body : JSON.stringify({
...form,
id: id === 'new' ? undefined : id // upsert
})
});
if (!res.ok) throw new Error(await res.text());
nav(-1);
} catch (err) {
console.error(err);
alert(err.message);
}
}
/* ---------- 5. render ---------- */
return (
<div className="max-w-lg mx-auto space-y-4">
<h2 className="text-2xl font-semibold">
{id === 'new' ? 'New' : 'Edit'} Career Profile
</h2>
{/* Scenario title */}
<label className="block">
<span className="font-medium">Scenario Title</span>
<input
name="scenario_title"
className="mt-1 w-full border rounded p-2"
placeholder="e.g. DataScientist Plan"
value={form.scenario_title}
onChange={handleChange}
/>
</label>
{/* Career picker (locked vs editable) */}
<label className="block font-medium">Career *</label>
{careerLocked ? (
<div className="flex items-center space-x-2">
<input
className="flex-1 border rounded p-2 bg-gray-100"
value={form.career_name}
disabled
/>
<button
type="button"
className="text-blue-600 underline text-sm"
onClick={unlockCareer}
>
Change
</button>
</div>
) : (
<CareerSearch onCareerSelected={handleCareerSelected} required />
)}
{/* Status */}
<label className="block">
<span className="font-medium">Status</span>
<select
name="status"
className="mt-1 w-full border rounded p-2"
value={form.status}
onChange={handleChange}
>
<option value="current">current</option>
<option value="future">future</option>
<option value="retired">retired</option>
</select>
</label>
{/* Dates */}
<label className="block">
<span className="font-medium">Start Date</span>
<input
type="date"
name="start_date"
className="mt-1 w-full border rounded p-2"
value={form.start_date}
onChange={handleChange}
/>
</label>
<label className="block">
<span className="font-medium">Retirement Start Date</span>
<input
type="date"
name="retirement_start_date"
className="mt-1 w-full border rounded p-2"
value={form.retirement_start_date}
onChange={handleChange}
/>
</label>
{/* College status */}
<label className="block">
<span className="font-medium">College Enrollment Status</span>
<select
name="college_enrollment_status"
className="mt-1 w-full border rounded p-2"
value={form.college_enrollment_status}
onChange={handleChange}
>
<option value="">-- select --</option>
<option value="not_applicable">Not Applicable</option>
<option value="prospective_student">Prospective Student</option>
<option value="currently_enrolled">Currently Enrolled</option>
<option value="completed">Completed</option>
</select>
</label>
{/* Career goals */}
<label className="block">
<span className="font-medium">Career Goals</span>
<textarea
rows={3}
name="career_goals"
className="mt-1 w-full border rounded p-2"
placeholder="e.g. Become a senior datascientist in five years…"
value={form.career_goals}
onChange={handleChange}
/>
</label>
{/* Desired retirement income */}
<label className="block">
<span className="font-medium">Desired Retirement Income / Month ($)</span>
<input
type="number"
name="desired_retirement_income_monthly"
className="mt-1 w-full border rounded p-2"
placeholder="e.g. 6000"
value={form.desired_retirement_income_monthly}
onChange={handleChange}
/>
</label>
{/* Action buttons */}
<div className="pt-4 flex justify-between">
<button
type="button"
onClick={() => nav(-1)}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Back
</button>
<button
type="button"
onClick={save}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded"
>
Save
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,79 @@
import React, { useEffect, useState } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
export default function CareerProfileList() {
const [rows, setRows] = useState([]);
const nav = useNavigate();
const token = localStorage.getItem('token');
useEffect(() => {
fetch('/api/premium/career-profile/all', {
headers: { Authorization: `Bearer ${token}` }
})
.then(r => r.json())
.then(d => setRows(d.careerProfiles || []));
}, [token]);
async function remove(id) {
if (!window.confirm('Delete this career profile?')) return;
await fetch(`/api/premium/career-profile/${id}`, {
method : 'DELETE',
headers: { Authorization: `Bearer ${token}` }
});
setRows(rows.filter(r => r.id !== id));
}
return (
<div className="max-w-4xl mx-auto space-y-4">
<h2 className="text-2xl font-semibold">Career Profiles</h2>
<button
onClick={() => nav('/profile/careers/new/edit')}
className="px-3 py-2 bg-blue-600 text-white rounded"
>
+ New Career Profile
</button>
<table className="w-full border mt-4 text-sm">
<thead className="bg-gray-100">
<tr>
<th className="p-2 text-left">Title</th>
<th className="p-2 text-left">Status</th>
<th className="p-2">Start</th>
<th className="p-2"></th>
</tr>
</thead>
<tbody>
{rows.map(r => (
<tr key={r.id} className="border-t">
<td className="p-2">{r.scenario_title || r.career_name}</td>
<td className="p-2">{r.status}</td>
<td className="p-2">{r.start_date}</td>
<td className="p-2 space-x-2">
<Link
to={`/profile/careers/${r.id}/edit`}
className="underline text-blue-600"
>
edit
</Link>
<button
onClick={() => remove(r.id)}
className="text-red-600 underline"
>
delete
</button>
</td>
</tr>
))}
{rows.length === 0 && (
<tr>
<td colSpan={5} className="p-4 text-center text-gray-500">
No career profiles yet
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}

View File

@ -0,0 +1,522 @@
import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import authFetch from '../utils/authFetch.js';
/** -----------------------------------------------------------
* Ensure numerics are sent as numbers and booleans as 0 / 1
* mirrors the logic you use in OnboardingContainer
* ---------------------------------------------------------- */
const parseFloatOrNull = v => {
if (v === '' || v == null) return null;
const n = parseFloat(v);
return Number.isFinite(n) ? n : null;
};
function normalisePayload(draft) {
const bools = [
'is_in_state','is_in_district','is_online',
'loan_deferral_until_graduation'
];
const nums = [
'annual_financial_aid','existing_college_debt','interest_rate','loan_term',
'extra_payment','expected_salary','credit_hours_per_year','hours_completed',
'credit_hours_required','program_length','tuition','tuition_paid'
];
const dates = ['enrollment_date', 'expected_graduation'];
const out = { ...draft };
bools.forEach(k => { out[k] = draft[k] ? 1 : 0; });
nums .forEach(k => { out[k] = parseFloatOrNull(draft[k]) ?? 0; });
dates.forEach(k => { out[k] = toMySqlDate(draft[k]); });
delete out.created_at;
delete out.updated_at;
return out;
}
const toMySqlDate = iso => {
if (!iso) return null;
return iso.replace('T', ' ').slice(0, 19);
};
export default function CollegeProfileForm() {
const { careerId, id } = useParams(); // id optional
const nav = useNavigate();
const token = localStorage.getItem('token');
const [cipRows, setCipRows] = useState([]);
const [schoolSug, setSchoolSug] = useState([]);
const [progSug, setProgSug] = useState([]);
const [types, setTypes] = useState([]);
const [ipeds, setIpeds] = useState([]);
const [schoolValid, setSchoolValid] = useState(true);
const [programValid, setProgramValid] = useState(true);
const schoolData = cipRows;
const [form, setForm] = useState({
career_profile_id : careerId,
selected_school : '',
selected_program : '',
program_type : '',
annual_financial_aid : 0,
tuition : 0,
interest_rate : 5.5,
loan_term : 10
});
const [manualTuition, setManualTuition] = useState(
form.tuition ? String(form.tuition) : ''
);
const [autoTuition, setAutoTuition] = useState(0);
// ---------- handlers (inside component) ----------
const handleFieldChange = (e) => {
const { name, value, type, checked } = e.target;
setForm((prev) => {
const draft = { ...prev };
if (type === 'checkbox') {
draft[name] = checked;
} else if (
[
'interest_rate','loan_term','extra_payment','expected_salary',
'annual_financial_aid','existing_college_debt','credit_hours_per_year',
'hours_completed','credit_hours_required','tuition','tuition_paid',
'program_length'
].includes(name)
) {
draft[name] = value === '' ? '' : parseFloat(value);
} else {
draft[name] = value;
}
return draft;
});
};
const onSchoolInput = (e) => {
handleFieldChange(e);
const v = e.target.value.toLowerCase();
const suggestions = cipRows
.filter((r) => r.INSTNM.toLowerCase().includes(v))
.map((r) => r.INSTNM);
setSchoolSug([...new Set(suggestions)].slice(0, 10));
};
const onProgramInput = (e) => {
handleFieldChange(e);
if (!form.selected_school) return;
const v = e.target.value.toLowerCase();
const sug = cipRows
.filter(
(r) =>
r.INSTNM.toLowerCase() === form.selected_school.toLowerCase() &&
r.CIPDESC.toLowerCase().includes(v)
)
.map((r) => r.CIPDESC);
setProgSug([...new Set(sug)].slice(0, 10));
};
useEffect(() => {
if (id && id !== 'new') {
fetch(`/api/premium/college-profile?careerProfileId=${careerId}`, {
headers: { Authorization: `Bearer ${token}` }
})
.then(r => r.json())
.then(setForm);
}
}, [careerId, id, token]);
async function handleSave(){
try{
const body = normalisePayload({ ...form, tuition: chosenTuition, career_profile_id: careerId });
const res = await authFetch('/api/premium/college-profile',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify(body)
});
if(!res.ok) throw new Error(await res.text());
alert('Saved!');
setForm(p => ({ ...p, tuition: chosenTuition }));
setManualTuition(String(chosenTuition));
nav(-1);
}catch(err){ console.error(err); alert(err.message);}
}
/* LOAD iPEDS ----------------------------- */
useEffect(() => {
fetch('/ic2023_ay.csv')
.then(r => r.text())
.then(text => {
const rows = text.split('\n').map(l => l.split(','));
const headers = rows[0];
const parsed = rows.slice(1).map(r =>
Object.fromEntries(r.map((v,i)=>[headers[i], v]))
);
setIpeds(parsed); // you already declared setIpeds
})
.catch(err => console.error('iPEDS load failed', err));
}, []);
useEffect(() => { fetch('/cip_institution_mapping_new.json')
.then(r=>r.text()).then(t => setCipRows(
t.split('\n').map(l=>{try{return JSON.parse(l)}catch{ return null }})
.filter(Boolean)
));
fetch('/ic2023_ay.csv')
.then(r=>r.text()).then(csv=>{/* identical to CollegeOnboarding */});
},[]);
useEffect(()=>{
if(!form.selected_school || !form.selected_program) { setTypes([]); return; }
const t = cipRows.filter(r =>
r.INSTNM.toLowerCase()===form.selected_school.toLowerCase() &&
r.CIPDESC===form.selected_program)
.map(r=>r.CREDDESC);
setTypes([...new Set(t)]);
},[form.selected_school, form.selected_program, cipRows]);
useEffect(() => {
if (!ipeds.length) return;
if (!form.selected_school ||
!form.program_type ||
!form.credit_hours_per_year) return;
/* 1 ─ locate UNITID */
const sch = cipRows.find(
r => r.INSTNM.toLowerCase() === form.selected_school.toLowerCase()
);
if (!sch) return;
const unitId = sch.UNITID;
const row = ipeds.find(r => r.UNITID === unitId);
if (!row) return;
/* 2 ─ decide instate / district buckets */
const grad = [
"Master's Degree","Doctoral Degree",
"Graduate/Professional Certificate","First Professional Degree"
].includes(form.program_type);
const pick = (codeInDist, codeInState, codeOut) => {
if (form.is_in_district) return row[codeInDist];
else if (form.is_in_state) return row[codeInState];
else return row[codeOut];
};
const partTime = grad
? pick('HRCHG5','HRCHG6','HRCHG7')
: pick('HRCHG1','HRCHG2','HRCHG3');
const fullTime = grad
? pick('TUITION5','TUITION6','TUITION7')
: pick('TUITION1','TUITION2','TUITION3');
const chpy = parseFloat(form.credit_hours_per_year) || 0;
const est = chpy && chpy < 24
? parseFloat(partTime || 0) * chpy
: parseFloat(fullTime || 0);
setAutoTuition(Math.round(est));
}, [
ipeds,
cipRows,
form.selected_school,
form.program_type,
form.credit_hours_per_year,
form.is_in_state,
form.is_in_district
]);
const handleManualTuitionChange = e => setManualTuition(e.target.value);
const chosenTuition = manualTuition.trim() === ''
? autoTuition
: parseFloat(manualTuition);
return (
<div className="max-w-md mx-auto p-6 space-y-4">
<h2 className="text-2xl font-semibold">
{id === 'new' ? 'New' : 'Edit'} College Plan
</h2>
{(form.college_enrollment_status === 'currently_enrolled' ||
form.college_enrollment_status === 'prospective_student') ? (
/* ───────────────────────────────────────────────────────── */
<div className="space-y-4">
{/* 1 │ Location / modality checkboxes */}
{[
{ n:'is_in_district', l:'In District?' },
{ n:'is_in_state', l:'InState Tuition?' },
{ n:'is_online', l:'Program is Fully Online' },
{ n:'loan_deferral_until_graduation',
l:'Defer Loan Payments untilGraduation?' }
].map(({n,l}) => (
<div key={n} className="flex items-center space-x-2">
<input type="checkbox" name={n}
className="h-4 w-4"
checked={!!form[n]}
onChange={handleFieldChange}/>
<label className="font-medium">{l}</label>
</div>
))}
{/* 2 │ School picker */}
<div className="space-y-1">
<label className="block font-medium">
School Name * (choose from list)
</label>
<input
name="selected_school"
value={form.selected_school}
onChange={onSchoolInput}
onBlur={() => {
const ok = cipRows.some(
r => r.INSTNM.toLowerCase() === form.selected_school.toLowerCase()
);
setSchoolValid(ok);
if (!ok) alert('Please pick a school from the list.');
}}
list="school-suggestions"
placeholder="Start typing and choose…"
className={`w-full border rounded p-2 ${schoolValid ? '' : 'border-red-500'}`}
required
/>
<datalist id="school-suggestions">
{schoolSug.map((s,i)=>(
<option key={i} value={s} />
))}
</datalist>
</div>
{/* 3 │ Program picker */}
<div className="space-y-1">
<label className="block font-medium">
Major / Program * (choose from list)
</label>
<input
name="selected_program"
value={form.selected_program}
onChange={onProgramInput}
onBlur={() => {
const ok =
form.selected_school && // need a school first
cipRows.some(
r =>
r.INSTNM.toLowerCase() === form.selected_school.toLowerCase() &&
r.CIPDESC.toLowerCase() === form.selected_program.toLowerCase()
);
setProgramValid(ok);
if (!ok) alert('Please pick a program from the list.');
}}
list="program-suggestions"
placeholder="Start typing and choose…"
className={`w-full border rounded p-2 ${programValid ? '' : 'border-red-500'}`}
required
/>
<datalist id="program-suggestions">
{progSug.map((p,i)=>(
<option key={i} value={p} />
))}
</datalist>
</div>
{/* 4 │ Programtype */}
<div className="space-y-1">
<label className="block font-medium">Degree Type *</label>
<select
name="program_type"
value={form.program_type}
onChange={handleFieldChange}
className="w-full border rounded p-2"
required
>
<option value="">Select Program Type</option>
{types.map((t,i)=><option key={i} value={t}>{t}</option>)}
</select>
</div>
{/* 5 │ Academic calendar */}
<div className="space-y-1">
<label className="block font-medium">Academic Calendar</label>
<select
name="academic_calendar"
value={form.academic_calendar || 'semester'}
onChange={handleFieldChange}
className="w-full border rounded p-2"
>
<option value="semester">Semester</option>
<option value="quarter">Quarter</option>
<option value="trimester">Trimester</option>
<option value="other">Other</option>
</select>
</div>
{/* 6 │ Credithour fields (conditionally rendered) */}
{(form.program_type === 'Graduate/Professional Certificate' ||
form.program_type === 'First Professional Degree' ||
form.program_type === 'Doctoral Degree' ||
form.program_type === 'Undergraduate Certificate or Diploma') && (
<div className="space-y-1">
<label className="block font-medium">Credit Hours Required</label>
<input
type="number"
name="credit_hours_required"
value={form.credit_hours_required}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
)}
<div className="space-y-1">
<label className="block font-medium">Credit Hours Per Year</label>
<input
type="number"
name="credit_hours_per_year"
value={form.credit_hours_per_year}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
{/* 7 │ Tuition & aid */}
<div className="space-y-1">
<label className="block font-medium">Yearly Tuition</label>
<input
type="number"
value={
manualTuition.trim() === '' ? autoTuition : manualTuition
}
onChange={handleManualTuitionChange}
placeholder="Blank = autocalculated"
className="w-full border rounded p-2"
/>
</div>
<span className="font-medium">Annual Aid</span>
<input
type="number"
name="annual_financial_aid"
value={form.annual_financial_aid}
onChange={handleFieldChange}
className="mt-1 w-full border rounded p-2"
/>
{/* 8 │ Existing debt */}
<div className="space-y-1">
<label className="block font-medium">Existing College Debt</label>
<input
type="number"
name="existing_college_debt"
value={form.existing_college_debt}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
{/* 9 │ Programlength & hourscompleted */}
{(form.college_enrollment_status === 'currently_enrolled' ||
form.college_enrollment_status === 'prospective_student') && (
<>
<div className="space-y-1">
<label className="block font-medium">Program Length (years)</label>
<input
type="number"
name="program_length"
value={form.program_length}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
{form.college_enrollment_status === 'currently_enrolled' && (
<div className="space-y-1">
<label className="block font-medium">Hours Completed</label>
<input
type="number"
name="hours_completed"
value={form.hours_completed}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
)}
</>
)}
{/* 10 │ Interest, term, extra payment, salary */}
<div className="flex space-x-4">
<label className="block flex-1">
<span className="font-medium">Interest %</span>
<input
type="number"
name="interest_rate"
value={form.interest_rate}
onChange={handleFieldChange}
className="mt-1 w-full border rounded p-2"
/>
</label>
<label className="block flex-1">
<span className="font-medium">Loan Term (years)</span>
<input
type="number"
name="loan_term"
value={form.loan_term}
onChange={handleFieldChange}
className="mt-1 w-full border rounded p-2"
/>
</label>
</div>
<div className="space-y-1">
<label className="block font-medium">Extra Monthly Payment</label>
<input
type="number"
name="extra_payment"
value={form.extra_payment}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-1">
<label className="block font-medium">Expected Salary After Graduation</label>
<input
type="number"
name="expected_salary"
value={form.expected_salary}
onChange={handleFieldChange}
className="w-full border rounded p-2"
/>
</div>
</div>
) : (
<p>
User is neither currently enrolled nor a prospective student nothing to
edit.
</p>
)}
{/* 11 │ Action buttons */}
<div className="pt-4">
<button
onClick={() => nav(-1)}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded mr-3"
>
 Back
</button>
<button
onClick={handleSave}
disabled={!schoolValid || !programValid}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded"
>
Save
</button>
</div>
</div>
);
}

View File

@ -0,0 +1,57 @@
import React, { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
export default function CollegeProfileList() {
const [rows, setRows] = useState([]);
const nav = useNavigate();
const token = localStorage.getItem('token');
useEffect(() => {
fetch('/api/premium/college-profile/all', {
headers: { Authorization: `Bearer ${token}` }
})
.then(r => r.json())
.then(d => setRows(d.collegeProfiles || []));
}, [token]);
return (
<div className="max-w-5xl mx-auto space-y-4">
<h2 className="text-2xl font-semibold">College Profiles</h2>
<table className="w-full border text-sm">
<thead className="bg-gray-100">
<tr>
<th className="p-2 text-left">Career</th>
<th className="p-2 text-left">School</th>
<th className="p-2 text-left">Program</th>
<th className="p-2">Created</th>
<th className="p-2"></th>
</tr>
</thead>
<tbody>
{rows.map(r => (
<tr key={r.id} className="border-t">
<td className="p-2">{r.career_title}</td>
<td className="p-2">{r.selected_school}</td>
<td className="p-2">{r.selected_program}</td>
<td className="p-2">{r.created_at?.slice(0,10)}</td>
<td className="p-2">
<Link
to={`/profile/college/${r.career_profile_id}/${r.id}`}
className="underline text-blue-600"
>
edit
</Link>
</td>
</tr>
))}
{rows.length === 0 && (
<tr><td colSpan={5} className="p-4 text-center text-gray-500">
No college profiles yet
</td></tr>
)}
</tbody>
</table>
</div>
);
}

View File

@ -1,181 +1,219 @@
// FinancialProfileForm.js
import React, { useState, useEffect } from 'react';
import authFetch from '../utils/authFetch.js';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
function FinancialProfileForm() {
// We'll store the fields in local state
const [currentSalary, setCurrentSalary] = useState('');
const [additionalIncome, setAdditionalIncome] = useState('');
const [monthlyExpenses, setMonthlyExpenses] = useState('');
const [monthlyDebtPayments, setMonthlyDebtPayments] = useState('');
const [retirementSavings, setRetirementSavings] = useState('');
const [emergencyFund, setEmergencyFund] = useState('');
const [retirementContribution, setRetirementContribution] = useState('');
const [monthlyEmergencyContribution, setMonthlyEmergencyContribution] = useState('');
const [extraCashEmergencyPct, setExtraCashEmergencyPct] = useState('');
const [extraCashRetirementPct, setExtraCashRetirementPct] = useState('');
import authFetch from '../utils/authFetch.js';
import Modal from './ui/modal.js';
import ExpensesWizard from './ExpensesWizard.js'; // same wizard you use in onboarding
import { Button } from './ui/button.js'; // Tailwindbased button (optional)
/* helper clamp 0100 */
const pct = v => Math.min(Math.max(parseFloat(v) || 0, 0), 100);
export default function FinancialProfileForm() {
const nav = useNavigate();
/* ─────────────── local state ─────────────── */
const [currentSalary, setCurrentSalary] = useState('');
const [additionalIncome, setAdditionalIncome] = useState('');
const [monthlyExpenses, setMonthlyExpenses] = useState('');
const [monthlyDebtPayments, setMonthlyDebtPayments] = useState('');
const [retirementSavings, setRetirementSavings] = useState('');
const [emergencyFund, setEmergencyFund] = useState('');
const [retirementContribution, setRetirementContribution] = useState('');
const [emergencyContribution, setEmergencyContribution] = useState('');
const [extraCashEmergencyPct, setExtraCashEmergencyPct] = useState('50');
const [extraCashRetirementPct, setExtraCashRetirementPct] = useState('50');
/* wizard modal */
const [showExpensesWizard, setShowExpensesWizard] = useState(false);
const openWizard = () => setShowExpensesWizard(true);
const closeWizard = () => setShowExpensesWizard(false);
/* ───────────── preload existing row ───────── */
useEffect(() => {
// On mount, fetch the user's existing profile from the new financial_profiles table
async function fetchProfile() {
(async () => {
try {
const res = await authFetch('/api/premium/financial-profile', {
method: 'GET'
});
if (res.ok) {
const data = await res.json();
// data might be an empty object if no row yet
setCurrentSalary(data.current_salary || '');
setAdditionalIncome(data.additional_income || '');
setMonthlyExpenses(data.monthly_expenses || '');
setMonthlyDebtPayments(data.monthly_debt_payments || '');
setRetirementSavings(data.retirement_savings || '');
setEmergencyFund(data.emergency_fund || '');
setRetirementContribution(data.retirement_contribution || '');
setMonthlyEmergencyContribution(data.monthly_emergency_contribution || '');
setExtraCashEmergencyPct(data.extra_cash_emergency_pct || '');
setExtraCashRetirementPct(data.extra_cash_retirement_pct || '');
}
} catch (err) {
console.error("Failed to load financial profile:", err);
}
}
fetchProfile();
const res = await authFetch('/api/premium/financial-profile');
if (!res.ok) return;
const d = await res.json();
setCurrentSalary (d.current_salary ?? '');
setAdditionalIncome (d.additional_income ?? '');
setMonthlyExpenses (d.monthly_expenses ?? '');
setMonthlyDebtPayments (d.monthly_debt_payments ?? '');
setRetirementSavings (d.retirement_savings ?? '');
setEmergencyFund (d.emergency_fund ?? '');
setRetirementContribution (d.retirement_contribution ?? '');
setEmergencyContribution (d.emergency_contribution ?? '');
setExtraCashEmergencyPct (d.extra_cash_emergency_pct ?? '');
setExtraCashRetirementPct (d.extra_cash_retirement_pct ?? '');
} catch (err) { console.error(err); }
})();
}, []);
// Submit form updates => POST to the same endpoint
async function handleSubmit(e) {
e.preventDefault();
try {
const body = {
current_salary: parseFloat(currentSalary) || 0,
additional_income: parseFloat(additionalIncome) || 0,
monthly_expenses: parseFloat(monthlyExpenses) || 0,
monthly_debt_payments: parseFloat(monthlyDebtPayments) || 0,
retirement_savings: parseFloat(retirementSavings) || 0,
emergency_fund: parseFloat(emergencyFund) || 0,
retirement_contribution: parseFloat(retirementContribution) || 0,
monthly_emergency_contribution: parseFloat(monthlyEmergencyContribution) || 0,
extra_cash_emergency_pct: parseFloat(extraCashEmergencyPct) || 0,
extra_cash_retirement_pct: parseFloat(extraCashRetirementPct) || 0
};
/* -----------------------------------------------------------
* keep the two % inputs complementary (must add to 100)
* --------------------------------------------------------- */
function handleChange(e) {
const { name, value } = e.target;
const pct = Math.max(0, Math.min(100, Number(value) || 0)); // clamp 0100
const res = await authFetch('/api/premium/financial-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (res.ok) {
// show success or redirect
console.log("Profile updated");
} else {
console.error("Failed to update profile:", await res.text());
if (name === 'extraCashEmergencyPct') {
setExtraCashEmergencyPct(String(pct));
setExtraCashRetirementPct(String(100 - pct));
} else if (name === 'extraCashRetirementPct') {
setExtraCashRetirementPct(String(pct));
setExtraCashEmergencyPct(String(100 - pct));
} else {
// all other numeric fields:
// allow empty string so users can clear then retype
const update = valSetter => valSetter(value === '' ? '' : Number(value));
switch (name) {
case 'currentSalary': update(setCurrentSalary); break;
case 'additionalIncome': update(setAdditionalIncome); break;
case 'monthlyExpenses': update(setMonthlyExpenses); break;
case 'monthlyDebtPayments': update(setMonthlyDebtPayments); break;
case 'retirementSavings': update(setRetirementSavings); break;
case 'emergencyFund': update(setEmergencyFund); break;
case 'retirementContribution': update(setRetirementContribution); break;
case 'emergencyContribution': update(setEmergencyContribution); break;
default: break;
}
} catch (err) {
console.error("Error submitting financial profile:", err);
}
}
/* ───────────── submit ─────────────────────── */
async function handleSubmit(e) {
e.preventDefault();
const body = {
current_salary: parseFloat(currentSalary) || 0,
additional_income: parseFloat(additionalIncome) || 0,
monthly_expenses: parseFloat(monthlyExpenses) || 0,
monthly_debt_payments: parseFloat(monthlyDebtPayments) || 0,
retirement_savings: parseFloat(retirementSavings) || 0,
emergency_fund: parseFloat(emergencyFund) || 0,
retirement_contribution: parseFloat(retirementContribution) || 0,
emergency_contribution: parseFloat(emergencyContribution) || 0,
extra_cash_emergency_pct: pct(extraCashEmergencyPct),
extra_cash_retirement_pct: pct(extraCashRetirementPct)
};
try {
const res = await authFetch('/api/premium/financial-profile', {
method : 'POST',
headers: { 'Content-Type':'application/json' },
body : JSON.stringify(body)
});
if (!res.ok) throw new Error(await res.text());
alert('Financial profile saved.');
nav(-1);
} catch (err) {
console.error(err);
alert('Failed to save financial profile.');
}
}
/* ───────────── view ───────────────────────── */
return (
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto p-4 space-y-4 bg-white shadow rounded">
<h2 className="text-xl font-semibold">Edit Your Financial Profile</h2>
<>
<form
onSubmit={handleSubmit}
className="max-w-2xl mx-auto p-6 space-y-4 bg-white shadow rounded"
>
<h2 className="text-xl font-semibold">EditYourFinancialProfile</h2>
<label className="block font-medium">Current Salary</label>
<input
type="number"
value={currentSalary}
onChange={(e) => setCurrentSalary(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
{/* salary / income */}
<label className="block font-medium">CurrentAnnualSalary</label>
<input type="number" className="w-full border rounded p-2"
name="currentSalary" value={currentSalary} onChange={handleChange} />
<label className="block font-medium">Additional Monthly Income</label>
<input
type="number"
value={additionalIncome}
onChange={(e) => setAdditionalIncome(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
<label className="block font-medium">AdditionalAnnualIncome</label>
<input type="number" className="w-full border rounded p-2"
name="additionalIncome" value={additionalIncome} onChange={handleChange} />
<label className="block font-medium">Monthly Living Expenses</label>
<input
type="number"
value={monthlyExpenses}
onChange={(e) => setMonthlyExpenses(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
{/* expenses with wizard */}
<label className="block font-medium">MonthlyLivingExpenses</label>
<div className="flex space-x-2 items-center">
<input type="number" className="w-full border rounded p-2"
value={monthlyExpenses}
onChange={e=>setMonthlyExpenses(e.target.value)} />
<Button className="bg-blue-600 text-white px-3 py-2 rounded"
type="button" onClick={openWizard}>
Need Help?
</Button>
</div>
<label className="block font-medium">Monthly Debt Payments</label>
<input
type="number"
value={monthlyDebtPayments}
onChange={(e) => setMonthlyDebtPayments(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
{/* rest of the numeric fields */}
<label className="block font-medium">MonthlyDebtPayments</label>
<input type="number" className="w-full border rounded p-2"
name="monthlyDebtPayments" value={monthlyDebtPayments} onChange={handleChange} />
<label className="block font-medium">Retirement Savings</label>
<input
type="number"
value={retirementSavings}
onChange={(e) => setRetirementSavings(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
<label className="block font-medium">RetirementSavings</label>
<input type="number" className="w-full border rounded p-2"
name="retirementSavings" value={retirementSavings} onChange={handleChange} />
<label className="block font-medium">Emergency Fund</label>
<input
type="number"
value={emergencyFund}
onChange={(e) => setEmergencyFund(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
<label className="block font-medium">EmergencyFund</label>
<input type="number" className="w-full border rounded p-2"
name="emergencyFund" value={emergencyFund} onChange={handleChange} />
<label className="block font-medium">Monthly Retirement Contribution</label>
<input
type="number"
value={retirementContribution}
onChange={(e) => setRetirementContribution(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
<label className="block font-medium">MonthlyRetirementContribution</label>
<input type="number" className="w-full border rounded p-2"
name="retirementContribution" value={retirementContribution} onChange={handleChange} />
<label className="block font-medium">Monthly Emergency Contribution</label>
<input
type="number"
value={monthlyEmergencyContribution}
onChange={(e) => setMonthlyEmergencyContribution(e.target.value)}
className="w-full border rounded p-2"
placeholder="$"
/>
<label className="block font-medium">MonthlyEmergencyContribution</label>
<input type="number" className="w-full border rounded p-2"
name="emergencyContribution"
value={emergencyContribution}
onChange={handleChange} />
<label className="block font-medium">Extra Cash to Emergency (%)</label>
<input
type="number"
value={extraCashEmergencyPct}
onChange={(e) => setExtraCashEmergencyPct(e.target.value)}
className="w-full border rounded p-2"
placeholder="e.g. 30"
/>
{/* allocation kept in sync */}
<h3 className="text-lg font-medium pt-2">ExtraMonthlyCashAllocation (must total100%)</h3>
<label className="block font-medium">Extra Cash to Retirement (%)</label>
<input
type="number"
value={extraCashRetirementPct}
onChange={(e) => setExtraCashRetirementPct(e.target.value)}
className="w-full border rounded p-2"
placeholder="e.g. 70"
/>
<label className="block font-medium">ToEmergencyFund(%)</label>
<input type="number" className="w-full border rounded p-2"
name="extraCashEmergencyPct"
value={extraCashEmergencyPct}
onChange={handleChange} />
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded">
Save and Continue
</button>
</form>
<label className="block font-medium">ToRetirement(%)</label>
<input type="number" className="w-full border rounded p-2"
name="extraCashRetirementPct"
value={extraCashRetirementPct}
onChange={handleChange} />
{/* action buttons */}
<div className="pt-4 flex justify-between">
<button
type="button"
onClick={()=>nav(-1)}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Back
</button>
<button
type="submit"
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded"
>
Save
</button>
</div>
</form>
{/* wizard modal */}
{showExpensesWizard && (
<Modal onClose={closeWizard}>
<ExpensesWizard
onClose={closeWizard}
onExpensesCalculated={total => {
setMonthlyExpenses(total);
closeWizard();
}}
/>
</Modal>
)}
</>
);
}
export default FinancialProfileForm;

View File

@ -33,14 +33,6 @@ const CareerOnboarding = ({ nextStep, prevStep, data, setData, finishNow }) => {
const skipFin = !!data.skipFinancialStep;
// 1) Grab the location state values, if any
const {
socCode,
cipCodes,
careerTitle, // <--- we passed this from handleSelectForEducation
userZip,
userState,
} = location.state || {};
/* ── 3. sideeffects when route brings a new career object ── */
useEffect(() => {
if (!navCareerObj?.title) return;
@ -166,17 +158,6 @@ function handleSubmit() {
/>
</div>
<div className="space-y-2">
<label className="block font-medium">Projected End Date (optional):</label>
<input
name="projected_end_date"
type="date"
onChange={handleChange}
value={data.projected_end_date || ''}
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-2">
<label className="block font-medium">
Are you currently enrolled in college or planning to enroll? <Req />

View File

@ -48,7 +48,6 @@ function ReviewPage({
<div><strong>College enrollment Status:</strong> {careerData.college_enrollment_status || 'N/A'}</div>
<div><strong>Status:</strong> {careerData.status || 'N/A'}</div>
<div><strong>Start Date:</strong> {careerData.start_date || 'N/A'}</div>
<div><strong>Projected End Date:</strong> {careerData.projected_end_date || 'N/A'}</div>
<div><strong>Career Goals:</strong> {careerData.career_goals || 'N/A'}</div>
</div>

View File

@ -15,8 +15,7 @@ function RetirementLanding() {
Plan strategically and financially for retirement. AptivaAI provides you with clear financial projections, milestone tracking, and scenario analysis for a secure future.
</p>
<div className="grid grid-cols-1 gap-4">
<Button onClick={() => navigate('/financial-profile')}>Update Financial Profile</Button>
<Button onClick={() => navigate('/retirement-planner')}>Set Retirement Milestones and get AI help with planning</Button>
<Button onClick={() => navigate('/retirement-planner')}>Compare different retirement scenarios and get AI help with planning</Button>
</div>
</div>
</div>

View File

@ -159,7 +159,6 @@ export default function ScenarioEditModal({
career_name : safe(s.career_name),
status : safe(s.status || 'planned'),
start_date : safe(s.start_date),
projected_end_date : safe(s.projected_end_date),
retirement_start_date: safe(s.retirement_start_date),
desired_retirement_income_monthly : safe(
s.desired_retirement_income_monthly
@ -498,7 +497,6 @@ async function handleSave() {
currently_working : formData.currently_working || "no",
status : s(formData.status),
start_date : s(formData.start_date),
projected_end_date : s(formData.projected_end_date),
retirement_start_date : s(formData.retirement_start_date),
desired_retirement_income_monthly : n(formData.desired_retirement_income_monthly),
@ -687,18 +685,6 @@ if (formData.retirement_start_date) {
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Projected End Date
</label>
<input
type="date"
name="projected_end_date"
value={formData.projected_end_date || ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
</div>
{/* Retirement date */}

View File

@ -51,7 +51,6 @@ export default function ScenarioEditWizard({
currently_working: scenData.currently_working,
status: scenData.status,
start_date: scenData.start_date,
projected_end_date: scenData.projected_end_date,
planned_monthly_expenses: scenData.planned_monthly_expenses,
planned_monthly_debt_payments: scenData.planned_monthly_debt_payments,
planned_monthly_retirement_contribution: scenData.planned_monthly_retirement_contribution,