220 lines
9.9 KiB
JavaScript
220 lines
9.9 KiB
JavaScript
// FinancialProfileForm.js
|
||
import React, { useEffect, useState } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
|
||
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'; // Tailwind‑based button (optional)
|
||
|
||
/* helper – clamp 0‑100 */
|
||
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(() => {
|
||
(async () => {
|
||
try {
|
||
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); }
|
||
})();
|
||
}, []);
|
||
|
||
/* -----------------------------------------------------------
|
||
* 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 0‑100
|
||
|
||
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 re‑type
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ───────────── 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-6 space-y-4 bg-white shadow rounded"
|
||
>
|
||
<h2 className="text-xl font-semibold">Edit Your Financial Profile</h2>
|
||
|
||
{/* salary / income */}
|
||
<label className="block font-medium">Current Annual Salary</label>
|
||
<input type="number" className="w-full border rounded p-2"
|
||
name="currentSalary" value={currentSalary} onChange={handleChange} />
|
||
|
||
<label className="block font-medium">Additional Annual Income</label>
|
||
<input type="number" className="w-full border rounded p-2"
|
||
name="additionalIncome" value={additionalIncome} onChange={handleChange} />
|
||
|
||
{/* expenses with wizard */}
|
||
<label className="block font-medium">Monthly Living Expenses</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>
|
||
|
||
{/* rest of the numeric fields */}
|
||
<label className="block font-medium">Monthly Debt Payments</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" className="w-full border rounded p-2"
|
||
name="retirementSavings" value={retirementSavings} onChange={handleChange} />
|
||
|
||
<label className="block font-medium">Emergency Fund</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" 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" className="w-full border rounded p-2"
|
||
name="emergencyContribution"
|
||
value={emergencyContribution}
|
||
onChange={handleChange} />
|
||
|
||
{/* allocation – kept in sync */}
|
||
<h3 className="text-lg font-medium pt-2">Extra Monthly Cash Allocation (must total 100%)</h3>
|
||
|
||
<label className="block font-medium">To Emergency Fund (%)</label>
|
||
<input type="number" className="w-full border rounded p-2"
|
||
name="extraCashEmergencyPct"
|
||
value={extraCashEmergencyPct}
|
||
onChange={handleChange} />
|
||
|
||
<label className="block font-medium">To Retirement (%)</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>
|
||
)}
|
||
</>
|
||
);
|
||
}
|