export function retirementSavingsCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Retirement Savings Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Current Situation Section
const currentSection = form.addSubform('current', { title: '๐ค Your Current Situation' });
currentSection.addRow(row => {
row.addInteger('currentAge', {
label: 'Current Age',
min: 18,
max: 80,
defaultValue: 30,
suffix: 'years',
isRequired: true
}, '1fr');
row.addInteger('retirementAge', {
label: 'Planned Retirement Age',
min: 50,
max: 85,
defaultValue: 65,
suffix: 'years',
isRequired: true
}, '1fr');
});
currentSection.addRow(row => {
row.addDecimal('currentSavings', {
label: 'Current Retirement Savings',
min: 0,
max: 50000000,
defaultValue: 50000,
prefix: '$',
placeholder: 'e.g. 50000',
isRequired: true,
tooltip: 'Total in 401(k), IRA, and other retirement accounts'
}, '1fr');
row.addDecimal('annualIncome', {
label: 'Annual Income',
min: 0,
max: 10000000,
defaultValue: 75000,
prefix: '$',
placeholder: 'e.g. 75000',
isRequired: true
}, '1fr');
});
// Contributions Section
const contributionsSection = form.addSubform('contributions', { title: '๐ต Monthly Contributions' });
contributionsSection.addRow(row => {
row.addDecimal('monthlyContribution', {
label: 'Your Monthly Contribution',
min: 0,
max: 50000,
defaultValue: 500,
prefix: '$',
placeholder: 'e.g. 500'
}, '1fr');
row.addDecimal('employerMatch', {
label: 'Employer Match (Monthly)',
min: 0,
max: 50000,
defaultValue: 250,
prefix: '$',
placeholder: 'e.g. 250',
tooltip: 'Typical match is 3-6% of salary'
}, '1fr');
});
contributionsSection.addRow(row => {
row.addSlider('contributionIncrease', {
label: 'Annual Contribution Increase',
min: 0,
max: 10,
step: 0.5,
defaultValue: 2,
suffix: '%',
tooltip: 'Increase contributions as your salary grows'
});
});
// Investment Strategy Section
const investmentSection = form.addSubform('investment', { title: '๐ Investment Strategy' });
investmentSection.addRow(row => {
row.addDropdown('riskProfile', {
label: 'Risk Profile',
options: [
{ id: 'conservative', name: 'Conservative (4% return)' },
{ id: 'moderate', name: 'Moderate (6% return)' },
{ id: 'balanced', name: 'Balanced (7% return)' },
{ id: 'growth', name: 'Growth (8% return)' },
{ id: 'aggressive', name: 'Aggressive (9% return)' }
],
defaultValue: 'balanced',
isRequired: true
}, '1fr');
row.addDecimal('expectedReturn', {
label: 'Expected Annual Return',
min: 1,
max: 15,
defaultValue: 7,
suffix: '%',
isReadOnly: () => true,
computedValue: () => {
const profile = investmentSection.dropdown('riskProfile')?.value() || 'balanced';
const returns: Record<string, number> = {
'conservative': 4,
'moderate': 6,
'balanced': 7,
'growth': 8,
'aggressive': 9
};
return returns[profile] || 7;
}
}, '1fr');
});
investmentSection.addRow(row => {
row.addDecimal('inflationRate', {
label: 'Expected Inflation Rate',
min: 0,
max: 10,
defaultValue: 3,
suffix: '%',
tooltip: 'Historical average is around 3%'
}, '1fr');
row.addDropdown('accountType', {
label: 'Primary Account Type',
options: [
{ id: '401k', name: '401(k) - Pre-tax' },
{ id: 'roth401k', name: 'Roth 401(k) - After-tax' },
{ id: 'ira', name: 'Traditional IRA' },
{ id: 'rothIra', name: 'Roth IRA' },
{ id: 'taxable', name: 'Taxable Brokerage' }
],
defaultValue: '401k'
}, '1fr');
});
// Retirement Goals Section
const goalsSection = form.addSubform('goals', { title: '๐ฏ Retirement Goals' });
goalsSection.addRow(row => {
row.addSlider('replacementRate', {
label: 'Income Replacement Target',
min: 50,
max: 100,
step: 5,
defaultValue: 80,
suffix: '%',
tooltip: 'Most experts recommend replacing 70-90% of pre-retirement income'
});
});
goalsSection.addRow(row => {
row.addDecimal('socialSecurity', {
label: 'Expected Social Security (Monthly)',
min: 0,
max: 10000,
defaultValue: 2000,
prefix: '$',
tooltip: 'Check ssa.gov for your estimate'
}, '1fr');
row.addDecimal('otherIncome', {
label: 'Other Retirement Income (Monthly)',
min: 0,
max: 50000,
defaultValue: 0,
prefix: '$',
tooltip: 'Pension, rental income, part-time work, etc.'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Projections Section
const projectionsSection = form.addSubform('projections', { title: '๐ฎ Retirement Projections', isCollapsible: false });
projectionsSection.addRow(row => {
row.addPriceDisplay('yearsToRetirement', {
label: 'Years Until Retirement',
computedValue: () => {
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
return Math.max(0, retirement - current);
},
variant: 'default',
prefix: '',
suffix: ' years'
}, '1fr');
row.addPriceDisplay('totalContributions', {
label: 'Total Contributions',
computedValue: () => {
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
const years = Math.max(0, retirement - current);
const monthly = (contributionsSection.decimal('monthlyContribution')?.value() || 500) +
(contributionsSection.decimal('employerMatch')?.value() || 250);
const increase = (contributionsSection.slider('contributionIncrease')?.value() || 2) / 100;
let total = 0;
let currentMonthly = monthly;
for (let year = 0; year < years; year++) {
total += currentMonthly * 12;
currentMonthly *= (1 + increase);
}
return Math.round(total);
},
variant: 'default'
}, '1fr');
});
projectionsSection.addRow(row => {
row.addPriceDisplay('projectedSavings', {
label: 'Projected Retirement Savings',
computedValue: () => {
const currentSavings = currentSection.decimal('currentSavings')?.value() || 50000;
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
const years = Math.max(0, retirement - current);
const monthly = (contributionsSection.decimal('monthlyContribution')?.value() || 500) +
(contributionsSection.decimal('employerMatch')?.value() || 250);
const increase = (contributionsSection.slider('contributionIncrease')?.value() || 2) / 100;
const profile = investmentSection.dropdown('riskProfile')?.value() || 'balanced';
const returns: Record<string, number> = {
'conservative': 0.04,
'moderate': 0.06,
'balanced': 0.07,
'growth': 0.08,
'aggressive': 0.09
};
const annualReturn = returns[profile] || 0.07;
const monthlyReturn = annualReturn / 12;
let balance = currentSavings;
let currentMonthly = monthly;
for (let year = 0; year < years; year++) {
for (let month = 0; month < 12; month++) {
balance = balance * (1 + monthlyReturn) + currentMonthly;
}
currentMonthly *= (1 + increase);
}
return Math.round(balance);
},
variant: 'success'
}, '1fr');
row.addPriceDisplay('inflationAdjusted', {
label: "In Today's Dollars",
computedValue: () => {
const currentSavings = currentSection.decimal('currentSavings')?.value() || 50000;
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
const years = Math.max(0, retirement - current);
const monthly = (contributionsSection.decimal('monthlyContribution')?.value() || 500) +
(contributionsSection.decimal('employerMatch')?.value() || 250);
const increase = (contributionsSection.slider('contributionIncrease')?.value() || 2) / 100;
const profile = investmentSection.dropdown('riskProfile')?.value() || 'balanced';
const inflation = (investmentSection.decimal('inflationRate')?.value() || 3) / 100;
const returns: Record<string, number> = {
'conservative': 0.04,
'moderate': 0.06,
'balanced': 0.07,
'growth': 0.08,
'aggressive': 0.09
};
const annualReturn = returns[profile] || 0.07;
const monthlyReturn = annualReturn / 12;
let balance = currentSavings;
let currentMonthly = monthly;
for (let year = 0; year < years; year++) {
for (let month = 0; month < 12; month++) {
balance = balance * (1 + monthlyReturn) + currentMonthly;
}
currentMonthly *= (1 + increase);
}
// Adjust for inflation
const adjustedBalance = balance / Math.pow(1 + inflation, years);
return Math.round(adjustedBalance);
},
variant: 'default'
}, '1fr');
});
// Monthly Income Section
const incomeSection = form.addSubform('income', { title: '๐ฐ Monthly Retirement Income', isCollapsible: false });
incomeSection.addRow(row => {
row.addPriceDisplay('monthlyFromSavings', {
label: 'From Your Savings (4% rule)',
computedValue: () => {
const currentSavings = currentSection.decimal('currentSavings')?.value() || 50000;
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
const years = Math.max(0, retirement - current);
const monthly = (contributionsSection.decimal('monthlyContribution')?.value() || 500) +
(contributionsSection.decimal('employerMatch')?.value() || 250);
const increase = (contributionsSection.slider('contributionIncrease')?.value() || 2) / 100;
const profile = investmentSection.dropdown('riskProfile')?.value() || 'balanced';
const returns: Record<string, number> = {
'conservative': 0.04,
'moderate': 0.06,
'balanced': 0.07,
'growth': 0.08,
'aggressive': 0.09
};
const annualReturn = returns[profile] || 0.07;
const monthlyReturn = annualReturn / 12;
let balance = currentSavings;
let currentMonthly = monthly;
for (let year = 0; year < years; year++) {
for (let month = 0; month < 12; month++) {
balance = balance * (1 + monthlyReturn) + currentMonthly;
}
currentMonthly *= (1 + increase);
}
// 4% annual withdrawal rate divided by 12 months
return Math.round((balance * 0.04) / 12);
},
variant: 'default',
tooltip: 'Based on the 4% safe withdrawal rate'
}, '1fr');
row.addPriceDisplay('otherMonthly', {
label: 'Social Security + Other',
computedValue: () => {
const ss = goalsSection.decimal('socialSecurity')?.value() || 2000;
const other = goalsSection.decimal('otherIncome')?.value() || 0;
return Math.round(ss + other);
},
variant: 'default'
}, '1fr');
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addPriceDisplay('totalMonthlyIncome', {
label: 'Total Monthly Income',
computedValue: () => {
const currentSavings = currentSection.decimal('currentSavings')?.value() || 50000;
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
const years = Math.max(0, retirement - current);
const monthly = (contributionsSection.decimal('monthlyContribution')?.value() || 500) +
(contributionsSection.decimal('employerMatch')?.value() || 250);
const increase = (contributionsSection.slider('contributionIncrease')?.value() || 2) / 100;
const profile = investmentSection.dropdown('riskProfile')?.value() || 'balanced';
const ss = goalsSection.decimal('socialSecurity')?.value() || 2000;
const other = goalsSection.decimal('otherIncome')?.value() || 0;
const returns: Record<string, number> = {
'conservative': 0.04,
'moderate': 0.06,
'balanced': 0.07,
'growth': 0.08,
'aggressive': 0.09
};
const annualReturn = returns[profile] || 0.07;
const monthlyReturn = annualReturn / 12;
let balance = currentSavings;
let currentMonthly = monthly;
for (let year = 0; year < years; year++) {
for (let month = 0; month < 12; month++) {
balance = balance * (1 + monthlyReturn) + currentMonthly;
}
currentMonthly *= (1 + increase);
}
const fromSavings = (balance * 0.04) / 12;
return Math.round(fromSavings + ss + other);
},
variant: 'large'
});
});
summarySection.addRow(row => {
row.addTextPanel('statusMessage', {
computedValue: () => {
const income = currentSection.decimal('annualIncome')?.value() || 75000;
const targetPercent = (goalsSection.slider('replacementRate')?.value() || 80) / 100;
const targetMonthly = (income * targetPercent) / 12;
const currentSavings = currentSection.decimal('currentSavings')?.value() || 50000;
const current = currentSection.integer('currentAge')?.value() || 30;
const retirement = currentSection.integer('retirementAge')?.value() || 65;
const years = Math.max(0, retirement - current);
const monthly = (contributionsSection.decimal('monthlyContribution')?.value() || 500) +
(contributionsSection.decimal('employerMatch')?.value() || 250);
const increase = (contributionsSection.slider('contributionIncrease')?.value() || 2) / 100;
const profile = investmentSection.dropdown('riskProfile')?.value() || 'balanced';
const ss = goalsSection.decimal('socialSecurity')?.value() || 2000;
const other = goalsSection.decimal('otherIncome')?.value() || 0;
const returns: Record<string, number> = {
'conservative': 0.04,
'moderate': 0.06,
'balanced': 0.07,
'growth': 0.08,
'aggressive': 0.09
};
const annualReturn = returns[profile] || 0.07;
const monthlyReturn = annualReturn / 12;
let balance = currentSavings;
let currentMonthly = monthly;
for (let year = 0; year < years; year++) {
for (let month = 0; month < 12; month++) {
balance = balance * (1 + monthlyReturn) + currentMonthly;
}
currentMonthly *= (1 + increase);
}
const fromSavings = (balance * 0.04) / 12;
const totalMonthly = fromSavings + ss + other;
const percentage = Math.round((totalMonthly / targetMonthly) * 100);
if (percentage >= 100) {
return `You're on track to meet ${percentage}% of your retirement goal.`;
} else {
return `You're projected to reach ${percentage}% of your goal. Consider increasing contributions.`;
}
},
customStyles: { 'font-size': '0.9rem', 'color': '#475569', 'text-align': 'center', 'font-weight': '500' }
});
});
summarySection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'This calculator provides estimates for educational purposes only. Consult a financial advisor for personalized retirement planning.',
customStyles: { 'font-size': '0.8rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Save My Plan'
});
}