Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const INITIAL_PROFILE: UserProfile = {
age: 65, // Retirement Start Age
baseAge: 55, // Current Age
filingStatus: FilingStatus.Single,
spouseAge: 0,
spouseSocialSecurity: 0,
spouseSocialSecurityStartAge: 67,
spendingNeed: 60000,
isSpendingReal: true,
assets: { traditionalIRA: 250000, rothIRA: 100000, rothBasis: 50000, brokerage: 150000, hsa: 25000 },
Expand Down
81 changes: 77 additions & 4 deletions src/components/features/InputSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,57 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
placeholder="Target Age"
/>
</div>
<div>
<div className="md:col-span-2">
<label htmlFor="filingStatus" className={labelClass}>Filing Status</label>
<select
id="filingStatus"
value={profile.filingStatus}
onChange={(e) => handleChange('filingStatus', e.target.value)}
onChange={(e) => {
const newStatus = e.target.value as FilingStatus;
if (newStatus === FilingStatus.Single) {
setProfile({
...profile,
filingStatus: newStatus,
spouseAge: 0,
spouseSocialSecurity: 0,
spouseSocialSecurityStartAge: 67,
});
} else {
setProfile({
...profile,
filingStatus: newStatus,
spouseAge: profile.spouseAge || 65,
spouseSocialSecurity: profile.spouseSocialSecurity || 24000,
spouseSocialSecurityStartAge: profile.spouseSocialSecurityStartAge || 67,
});
}
}}
className={inputClass}
>
<option value={FilingStatus.Single}>Single</option>
<option value={FilingStatus.MarriedJoint}>Married Filing Jointly</option>
</select>
</div>
{profile.filingStatus === FilingStatus.MarriedJoint && (
<div className="md:col-span-2">
<label htmlFor="spouseAge" className={labelClass}>
Partner's Retirement Age
<Tooltip content="Your partner's age when you begin retirement. Used for the age-65+ deduction — each person 65+ gets an extra deduction." />
</label>
<input
id="spouseAge"
type="number"
value={profile.spouseAge || 65}
onChange={(e) => {
const rawValue = e.target.value;
const updatedValue = rawValue === '' ? 65 : parseInt(rawValue, 10);
handleChange('spouseAge', updatedValue);
}}
className={inputClass}
placeholder="65"
/>
</div>
)}
<div className="md:col-span-2">
<div className="flex items-center justify-between mb-1">
<label htmlFor="spendingNeed" className="text-sm font-medium text-slate-600 dark:text-slate-300 flex items-center gap-1">
Expand Down Expand Up @@ -317,7 +356,9 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="socialSecurity" className={labelClass}>Social Security Benefit</label>
<label htmlFor="socialSecurity" className={labelClass}>
{profile.filingStatus === FilingStatus.MarriedJoint ? 'Your SS Benefit' : 'Social Security Benefit'}
</label>
<div className="relative">
<span className={iconClass}>$</span>
<FormattedNumberInput
Expand All @@ -329,7 +370,9 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
</div>
</div>
<div>
<label htmlFor="ssStartAge" className={labelClass}>SS Start Age</label>
<label htmlFor="ssStartAge" className={labelClass}>
{profile.filingStatus === FilingStatus.MarriedJoint ? 'Your SS Start Age' : 'SS Start Age'}
</label>
<div className="relative">
<input
id="ssStartAge"
Expand All @@ -340,6 +383,36 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
/>
</div>
</div>
{profile.filingStatus === FilingStatus.MarriedJoint && (
<>
<div>
<label htmlFor="spouseSS" className={labelClass}>
Partner SS Benefit
<Tooltip content="Partner's expected annual Social Security benefit. Enter the annual amount." />
</label>
<div className="relative">
<span className={iconClass}>$</span>
<FormattedNumberInput
id="spouseSS"
value={profile.spouseSocialSecurity || 0}
onChange={(val) => handleChange('spouseSocialSecurity', val)}
className={`${inputClass} pl-8`}
/>
</div>
</div>
<div>
<label htmlFor="spouseSSAge" className={labelClass}>Partner SS Start Age</label>
<input
id="spouseSSAge"
type="number"
value={profile.spouseSocialSecurityStartAge || 67}
onChange={(e) => handleChange('spouseSocialSecurityStartAge', parseInt(e.target.value) || 67)}
className={inputClass}
placeholder="67"
/>
</div>
</>
)}
<div className="md:col-span-2">
<label htmlFor="pension" className={labelClass}>Pension / Annuity</label>
<div className="relative">
Expand Down
69 changes: 61 additions & 8 deletions src/components/features/wizard/Steps/Step6Income.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { WizardState } from '../types';
import { Activity, DollarSign } from 'lucide-react';
import { FilingStatus } from '../../../../types';
import { Activity, DollarSign, Users } from 'lucide-react';

interface CheckProps {
data: WizardState;
Expand All @@ -9,7 +10,7 @@ interface CheckProps {

const Step6Income: React.FC<CheckProps> = ({ data, update }) => {
// Ensure futureIncome object exists
const income = data.futureIncome || { socialSecurity: 0, socialSecurityStartAge: 62, pension: 0 };
const income = data.futureIncome || { socialSecurity: 0, socialSecurityStartAge: 62, pension: 0, spouseSocialSecurity: 0, spouseSocialSecurityStartAge: 67 };

// Local state for formatted inputs
// Initialize monthly SS display by dividing annual amount by 12
Expand All @@ -20,6 +21,16 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {
const [ssStartAgeDisplay, setSsStartAgeDisplay] = React.useState((income.socialSecurityStartAge || 62).toString());
const [pensionDisplay, setPensionDisplay] = React.useState((income.pension || 0).toLocaleString());

// Spouse SS display states
const [spouseSSDisplay, setSpouseSSDisplay] = React.useState(
income.spouseSocialSecurity ? Math.round((income.spouseSocialSecurity || 0) / 12).toLocaleString() : '0'
);
const [spouseSSStartAgeDisplay, setSpouseSSStartAgeDisplay] = React.useState(
(income.spouseSocialSecurityStartAge || 67).toString()
);

const isMFJ = data.filingStatus === FilingStatus.MarriedJoint;

const handleDisplayChange = (
field: keyof typeof income,
value: string,
Expand All @@ -42,8 +53,9 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {

setter(Number(raw).toLocaleString());

// If updating Social Security, convert monthly input to annual for the state
const valueToSave = field === 'socialSecurity' ? Number(raw) * 12 : Number(raw);
// If updating Social Security (primary or spouse), convert monthly input to annual
const valueToSave = (field === 'socialSecurity' || field === 'spouseSocialSecurity')
? Number(raw) * 12 : Number(raw);

update({
futureIncome: {
Expand All @@ -53,6 +65,8 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {
});
};

const inputClass = "w-full text-lg font-bold p-4 pl-8 rounded-xl border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 focus:ring-2 focus:ring-purple-500 outline-none transition-all";

return (
<div className="space-y-6 animate-in slide-in-from-right duration-500">
<div className="text-center space-y-2">
Expand All @@ -68,15 +82,15 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {
<div className="max-w-md mx-auto space-y-6 pt-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Estimated Monthly Social Security
{isMFJ ? 'Your Estimated Monthly Social Security' : 'Estimated Monthly Social Security'}
</label>
<div className="relative mb-2">
<span className="absolute left-4 top-1/2 transform -translate-y-1/2 text-slate-400 font-bold">$</span>
<input
type="text"
value={ssDisplay}
onChange={(e) => handleDisplayChange('socialSecurity', e.target.value, setSsDisplay)}
className="w-full text-lg font-bold p-4 pl-8 rounded-xl border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 focus:ring-2 focus:ring-purple-500 outline-none transition-all"
className={inputClass}
placeholder="0"
/>
</div>
Expand All @@ -87,7 +101,7 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {

<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Start Age (e.g. 62, 67, 70)
{isMFJ ? 'Your SS Start Age (e.g. 62, 67, 70)' : 'Start Age (e.g. 62, 67, 70)'}
</label>
<div className="relative mb-2">
<input
Expand All @@ -100,6 +114,45 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {
</div>
</div>

{/* Partner SS fields — visible only for MFJ */}
{isMFJ && (
<div className="border-t border-slate-200 dark:border-slate-700 pt-6 space-y-4">
<div className="flex items-center gap-2 mb-2">
<Users className="w-4 h-4 text-purple-500" />
<p className="text-sm font-bold text-purple-600 dark:text-purple-400">Partner's Social Security</p>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Partner's Monthly Social Security
</label>
<div className="relative mb-2">
<span className="absolute left-4 top-1/2 transform -translate-y-1/2 text-slate-400 font-bold">$</span>
<input
type="text"
value={spouseSSDisplay}
onChange={(e) => handleDisplayChange('spouseSocialSecurity', e.target.value, setSpouseSSDisplay)}
className={inputClass}
placeholder="0"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Partner's SS Start Age (e.g. 62, 67, 70)
</label>
<div className="relative mb-2">
<input
type="text"
value={spouseSSStartAgeDisplay}
onChange={(e) => handleDisplayChange('spouseSocialSecurityStartAge', e.target.value, setSpouseSSStartAgeDisplay)}
className="w-full text-lg font-bold p-4 pl-4 rounded-xl border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 focus:ring-2 focus:ring-purple-500 outline-none transition-all"
placeholder="67"
/>
</div>
</div>
</div>
)}

<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
Annual Pension / Annuity
Expand All @@ -110,7 +163,7 @@ const Step6Income: React.FC<CheckProps> = ({ data, update }) => {
type="text"
value={pensionDisplay}
onChange={(e) => handleDisplayChange('pension', e.target.value, setPensionDisplay)}
className="w-full text-lg font-bold p-4 pl-8 rounded-xl border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 focus:ring-2 focus:ring-purple-500 outline-none transition-all"
className={inputClass}
placeholder="0"
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/features/wizard/Steps/Step8Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface CheckProps {

const DUMMY_PROFILE: UserProfile = {
age: 65, baseAge: 55, filingStatus: FilingStatus.Single,
spouseAge: 0, spouseSocialSecurity: 0, spouseSocialSecurityStartAge: 67,
spendingNeed: 0, isSpendingReal: true,
assets: { traditionalIRA: 0, rothIRA: 0, rothBasis: 0, brokerage: 0, hsa: 0 },
contributions: { traditionalIRA: 0, rothIRA: 0, brokerage: 0, hsa: 0 },
Expand Down
2 changes: 1 addition & 1 deletion src/components/features/wizard/WizardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const INITIAL_WIZARD_STATE: WizardState = {
annualSpending: 60000,
totalAnnualContribution: 25000,
contributionAllocation: { taxDeferred: 50, taxable: 30, taxExempt: 20 },
futureIncome: { socialSecurity: 35000, socialSecurityStartAge: 67, pension: 0 }
futureIncome: { socialSecurity: 35000, socialSecurityStartAge: 67, pension: 0, spouseSocialSecurity: 0, spouseSocialSecurityStartAge: 67 }
};

const WizardModal: React.FC<WizardModalProps> = ({ isOpen, onClose, onComplete, currentProfile }) => {
Expand Down
2 changes: 2 additions & 0 deletions src/components/features/wizard/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ export interface WizardState {
socialSecurity: number;
socialSecurityStartAge: number;
pension: number;
spouseSocialSecurity: number;
spouseSocialSecurityStartAge: number;
};
}
3 changes: 3 additions & 0 deletions src/components/features/wizard/wizardMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const mapWizardStateToProfile = (wizardState: WizardState, currentProfile
age: retirementAge,
baseAge: currentAge,
filingStatus: filingStatus,
spouseAge: currentProfile.spouseAge || 0,
spouseSocialSecurity: futureIncome.spouseSocialSecurity || 0,
spouseSocialSecurityStartAge: futureIncome.spouseSocialSecurityStartAge || 67,
spendingNeed: annualSpending,
isSpendingReal: true, // Defaulting to today's dollars as per requirements
assets: {
Expand Down
Loading