dev1/src/components/PremiumOnboarding/FinancialOnboarding.js

345 lines
12 KiB
JavaScript

// FinancialOnboarding.js
import React, { useState } from 'react';
import Modal from '../ui/modal.js';
import ExpensesWizard from '../../components/ExpensesWizard.js'; // path to your wizard
import { Button } from '../../components/ui/button.js'; // using your Tailwind-based button
import { saveDraft, clearDraft, loadDraft } from '../../utils/onboardingDraftApi.js';
const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => {
const {
currently_working = '',
current_salary = 0,
additional_income = 0,
monthly_expenses = 0,
monthly_debt_payments = 0,
retirement_savings = 0,
retirement_contribution = 0,
emergency_fund = 0,
emergency_contribution = 0,
extra_cash_emergency_pct = 50,
extra_cash_retirement_pct = 50,
} = data;
const [showExpensesWizard, setShowExpensesWizard] = useState(false);
const handleNeedHelpExpenses = () => {
setShowExpensesWizard(true);
};
const handleExpensesCalculated = (total) => {
setData(prev => ({...prev, monthly_expenses: total }));
saveDraft({ financialData: { monthly_expenses: total } }).catch(() => {});
};
const infoIcon = (msg) => (
<span
className="ml-1 inline-flex h-4 w-4 items-center justify-center rounded-full bg-blue-500 text-white text-xs cursor-help"
title={msg}
>
i
</span>
);
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
let val = parseFloat(value) || 0;
if (type === 'checkbox') {
val = checked;
}
if (name === 'extra_cash_emergency_pct') {
val = Math.min(Math.max(val, 0), 100);
setData(prevData => ({
...prevData,
extra_cash_emergency_pct: val,
extra_cash_retirement_pct: 100 - val
}));
saveDraft({
financialData: {
extra_cash_emergency_pct: val,
extra_cash_retirement_pct: 100 - val
}
}).catch(() => {});
} else if (name === 'extra_cash_retirement_pct') {
val = Math.min(Math.max(val, 0), 100);
setData(prevData => ({
...prevData,
extra_cash_retirement_pct: val,
extra_cash_emergency_pct: 100 - val
}));
saveDraft({
financialData: {
extra_cash_retirement_pct: val,
extra_cash_emergency_pct: 100 - val
}
}).catch(() => {});
} else {
setData(prev => ({ ...prev, [name]: val }));
saveDraft({
financialData: {
[name]: val,
extra_cash_emergency_pct: Number.isFinite(extra_cash_emergency_pct) ? extra_cash_emergency_pct : 50,
extra_cash_retirement_pct: Number.isFinite(extra_cash_retirement_pct) ? extra_cash_retirement_pct : 50
}
}).catch(()=>{});
setData(prev => ({ ...prev, [name]: val }));
// Persist with 50/50 fallback if split is invalid or both 0
let ePct = Number(extra_cash_emergency_pct);
let rPct = Number(extra_cash_retirement_pct);
if (!Number.isFinite(ePct)) ePct = 0;
if (!Number.isFinite(rPct)) rPct = 0;
if ((ePct + rPct) === 0) { ePct = 50; rPct = 50; }
saveDraft({ financialData: {
[name]: val,
extra_cash_emergency_pct: ePct,
extra_cash_retirement_pct: rPct
}}).catch(()=>{});
}
};
const handleSubmit = () => {
// Final guard: coerce to numbers, clamp, and 50/50 if both resolve to 0
let ePct = Number(extra_cash_emergency_pct);
let rPct = Number(extra_cash_retirement_pct);
if (!Number.isFinite(ePct)) ePct = 0;
if (!Number.isFinite(rPct)) rPct = 0;
ePct = Math.min(Math.max(ePct, 0), 100);
rPct = Math.min(Math.max(rPct, 0), 100);
if ((ePct + rPct) === 0) { ePct = 50; rPct = 50; }
saveDraft({ financialData: {
extra_cash_emergency_pct: ePct,
extra_cash_retirement_pct: rPct
}}).catch(()=>{});
nextStep();
};
return (
<div className="max-w-md mx-auto p-6 space-y-6">
<h2 className="text-2xl font-semibold">Financial Details</h2>
{currently_working === 'yes' && (
<div className="space-y-4">
<div>
<label className="block font-medium">Current Annual Salary
{infoIcon("Gross annual salary before taxes")}
</label>
<input
name="current_salary"
type="number"
placeholder="e.g. 110000"
value={current_salary || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { current_salary: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Additional Annual Income (optional)
{infoIcon("Yearly bonuses, investment returns, side jobs, etc.")}
</label>
<input
name="additional_income"
type="number"
placeholder="e.g. 2400"
value={additional_income || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { additional_income: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
</div>
)}
<div className="space-y-4">
<label className="block font-medium"> Monthly Expenses
{infoIcon("The total amount you spend on rent, utilities, groceries, etc.")}
</label>
<div className="flex space-x-2 items-center">
<input
type="number"
className="w-full border rounded p-2"
name="monthly_expenses"
value={monthly_expenses}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { monthly_expenses: v } }).catch(() => {});
}}
placeholder="e.g. 1500"
/>
<Button
className="bg-blue-600 text-center px-3 py-2 rounded"
onClick={handleNeedHelpExpenses}
>
Need Help?
</Button>
</div>
<div>
<label className="block font-medium">Monthly Debt Payments (optional)
{infoIcon("If you keep installment loans on cars, credit cards, etc. separate from other living expenses")}
</label>
<input
name="monthly_debt_payments"
type="number"
placeholder="e.g. 500"
value={monthly_debt_payments || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { monthly_debt_payments: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Retirement Savings (optional)
{infoIcon("Current Retirement Balance")}
</label>
<input
name="retirement_savings"
type="number"
placeholder="e.g. 50000"
value={retirement_savings || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { retirement_savings: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Monthly Retirement Contribution (optional)
{infoIcon("Dollar value (not percentage) of monthly retirement contribution")}
</label>
<input
name="retirement_contribution"
type="number"
placeholder="e.g. 300"
value={retirement_contribution || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { retirement_contribution: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Emergency Fund Savings (optional)
{infoIcon("Balance of your emergency fund for job loss, medical emergencies, etc.")}
</label>
<input
name="emergency_fund"
type="number"
placeholder="e.g. 10000"
value={emergency_fund || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { emergency_fund: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Monthly Emergency Savings Contribution (optional)
{infoIcon("Dollar value (not percentage) of monthly emergency savings contribution")}
</label>
<input
name="emergency_contribution"
type="number"
placeholder="e.g. 300"
value={emergency_contribution || ''}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 0;
saveDraft({ financialData: { emergency_contribution: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
</div>
<div className="space-y-2">
<h3 className="text-lg font-medium">Extra Monthly Cash Allocation</h3>
<p className="text-gray-600">
If you have extra money left each month after expenses, how would you like to allocate it?
(Must add to 100%)
</p>
<div>
<label className="block font-medium">Extra Monthly Cash to Emergency Fund (%)</label>
<input
name="extra_cash_emergency_pct"
type="number"
placeholder="% to Emergency Savings (e.g., 30)"
value={extra_cash_emergency_pct}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 50;
saveDraft({ financialData: { extra_cash_emergency_pct: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Extra Monthly Cash to Retirement Fund (%)</label>
<input
name="extra_cash_retirement_pct"
type="number"
placeholder="% to Retirement Savings (e.g., 70)"
value={extra_cash_retirement_pct}
onChange={handleChange}
onBlur={(e) => {
const v = parseFloat(e.target.value) || 50;
saveDraft({ financialData: { extra_cash_retirement_pct: v } }).catch(() => {});
}}
className="w-full border rounded p-2"
/>
</div>
</div>
<div className="flex justify-between pt-4">
<button
onClick={prevStep}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Previous: Career
</button>
<button
onClick={handleSubmit}
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
>
Next: College
</button>
{showExpensesWizard && (
<Modal onClose={() => setShowExpensesWizard(false)}>
<ExpensesWizard
onClose={() => setShowExpensesWizard(false)}
onExpensesCalculated={handleExpensesCalculated}
/>
</Modal>
)}
</div>
</div>
);
};
export default FinancialOnboarding;