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
8 changes: 5 additions & 3 deletions src/components/features/AccumulationStrategy.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState, useMemo } from 'react';
import { UserProfile } from '../../types';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts';
import { Calendar, Wallet, TrendingUp, Table as TableIcon, Info, Rocket, ArrowRight, PieChart as PieIcon } from 'lucide-react';
import Tooltip from '../common/Tooltip';

interface AccumulationStrategyProps {
profile: UserProfile;
Expand Down Expand Up @@ -113,6 +114,7 @@ const AccumulationStrategy: React.FC<AccumulationStrategyProps> = ({ profile, se
<div className="flex items-center gap-1 bg-slate-100 dark:bg-slate-800 p-1 rounded-lg border border-slate-200 dark:border-slate-700">
<button onClick={() => setIsInflationAdjusted(false)} className={`px-4 py-2 text-xs font-bold rounded-md min-h-[36px] transition-colors ${!isInflationAdjusted ? 'bg-white dark:bg-slate-700 text-blue-600 shadow-sm' : 'text-slate-500 hover:text-slate-700'}`}>Nominal</button>
<button onClick={() => setIsInflationAdjusted(true)} className={`px-4 py-2 text-xs font-bold rounded-md min-h-[36px] transition-colors ${isInflationAdjusted ? 'bg-white dark:bg-slate-700 text-blue-600 shadow-sm' : 'text-slate-500 hover:text-slate-700'}`}>Adj Inflation's $</button>
<Tooltip content="Shows future portfolio balances adjusted for inflation (Today's Purchasing Power) versus unadjusted Nominal values." className="mr-1" />
</div>
</div>
<div className="flex flex-col sm:flex-row gap-6 items-center">
Expand Down Expand Up @@ -184,7 +186,7 @@ const AccumulationStrategy: React.FC<AccumulationStrategyProps> = ({ profile, se
tick={{ fontSize: 10 }}
width={50}
/>
<Tooltip
<RechartsTooltip
contentStyle={{ backgroundColor: tooltipBg, borderColor: gridColor, color: tooltipText, borderRadius: '10px', fontSize: '12px' }}
formatter={(value: number) => `$${value.toLocaleString()}`}
labelFormatter={(label) => `Age ${label}`}
Expand Down Expand Up @@ -231,7 +233,7 @@ const AccumulationStrategy: React.FC<AccumulationStrategyProps> = ({ profile, se
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip
<RechartsTooltip
formatter={(value: number) => `$${value.toLocaleString()}`}
contentStyle={{ backgroundColor: tooltipBg, color: tooltipText, borderRadius: '8px' }}
/>
Expand Down
8 changes: 7 additions & 1 deletion src/components/features/FireAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useState, useMemo } from 'react';
import { UserProfile } from '../../types';
import { calculateFireMilestones } from '../../services/fireCalculations';
import { FireInputs } from '../types/fire';
import { FireInputs } from '../../types/fire';
import { Flame, TrendingUp, DollarSign, Calendar, RefreshCw } from 'lucide-react';
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip as RechartsTooltip } from 'recharts';
import Tooltip from '../common/Tooltip';

interface FireAnalysisProps {
profile: UserProfile;
Expand Down Expand Up @@ -334,6 +335,11 @@ const FireAnalysis: React.FC<FireAnalysisProps> = ({ profile, isDarkMode }) => {
<div className="flex items-center justify-between mb-1">
<h4 className="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
{milestone.type} FIRE
{milestone.type === 'Lean' && <Tooltip content="Goal: Minimalist living expenses covered." className="ml-1" />}
{milestone.type === 'Standard' && <Tooltip content="Goal: Current lifestyle technically 'independent'." className="ml-1" />}
{milestone.type === 'Fat' && <Tooltip content="Goal: Luxury lifestyle with buffer." className="ml-1" />}
{milestone.type === 'Coast' && <Tooltip content="Goal: Saved enough to stop contributing today." className="ml-1" />}
{milestone.type === 'Barista' && <Tooltip content="Assumes you work part-time to earn supplemental income." className="ml-1" />}
{milestone.percentageProgress >= 100 && (
<span className="px-2 py-0.5 text-[10px] font-bold uppercase bg-emerald-100 dark:bg-emerald-900/30 text-emerald-600 dark:text-emerald-400 rounded-full">
Achieved
Expand Down
39 changes: 29 additions & 10 deletions src/components/features/InputSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { UserProfile, FilingStatus } from '../../types';
import { HelpCircle, DollarSign, Briefcase, Activity, TrendingUp, PiggyBank, RotateCcw, PlusCircle, AlertTriangle, Calculator } from 'lucide-react';
import PortfolioSelectorModal from '../modals/PortfolioSelectorModal';
import Tooltip from '../common/Tooltip';

interface InputSectionProps {
profile: UserProfile;
Expand Down Expand Up @@ -90,6 +91,7 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest

// const isFutureScenario = (Number(profile.age) || 0) !== profile.baseAge;
const [activeModal, setActiveModal] = useState<'accumulation' | 'retirement' | null>(null);
const [showRestartConfirm, setShowRestartConfirm] = useState(false);

const accumulationYears = Math.max(5, (profile.age || 65) - (profile.baseAge || 30)); // Min 5 years to show meaningful data
const retirementLongevityYears = Math.max(30, 95 - (profile.age || 65)); // Plan for at least 30 years or until age 95
Expand All @@ -106,12 +108,20 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
</h2>
</div>
<div className="flex items-center gap-2 flex-wrap">
<button
onClick={onRestartWizard}
className="text-xs font-semibold text-blue-600 dark:text-blue-400 hover:text-blue-800 bg-blue-50 dark:bg-blue-900/30 px-3 py-2 rounded-lg transition-colors min-h-[36px] border border-blue-100 dark:border-blue-800"
>
Restart Guided Wizard
</button>
{!showRestartConfirm ? (
<button
onClick={() => setShowRestartConfirm(true)}
className="text-xs font-semibold text-blue-600 dark:text-blue-400 hover:text-blue-800 bg-blue-50 dark:bg-blue-900/30 px-3 py-2 rounded-lg transition-colors min-h-[36px] border border-blue-100 dark:border-blue-800"
>
Restart Guided Wizard
</button>
) : (
<div className="flex items-center gap-2 bg-red-50 dark:bg-red-900/30 p-1.5 rounded-lg border border-red-100 dark:border-red-800">
<span className="text-xs text-red-600 dark:text-red-400 font-bold ml-1">Are you sure?</span>
<button onClick={onRestartWizard} className="text-xs bg-red-600 hover:bg-red-700 text-white px-3 py-1.5 rounded transition-colors font-medium">Yes</button>
<button onClick={() => setShowRestartConfirm(false)} className="text-xs bg-slate-200 hover:bg-slate-300 dark:bg-slate-700 dark:hover:bg-slate-600 text-slate-800 dark:text-slate-200 px-3 py-1.5 rounded transition-colors font-medium">No</button>
</div>
)}
{profile.age < profile.baseAge && (
<div className="flex items-center gap-1 text-xs font-bold text-red-600 bg-red-50 dark:bg-red-900/30 px-2 py-1.5 rounded-md border border-red-200 dark:border-red-800">
<AlertTriangle className="w-3 h-3" />
Expand Down Expand Up @@ -167,7 +177,10 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
</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">Annual Spending Need</label>
<label htmlFor="spendingNeed" className="text-sm font-medium text-slate-600 dark:text-slate-300">
Annual Spending Need
<Tooltip content="Estimated annual retirement living expenses (net of taxes). The strategy engine will calculate the gross withdrawal needed to cover this amount plus estimated federal taxes." className="ml-1" />
</label>
<div className="flex bg-slate-100 dark:bg-slate-800 p-1 rounded-lg border border-slate-200 dark:border-slate-700 text-xs font-bold">
<button
onClick={() => handleChange('isSpendingReal', true)}
Expand All @@ -177,6 +190,9 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
onClick={() => handleChange('isSpendingReal', false)}
className={`px-3 py-1.5 rounded-md min-h-[32px] transition-colors ${!profile.isSpendingReal ? 'bg-white dark:bg-slate-600 text-blue-600 dark:text-blue-300 shadow-sm' : 'text-slate-500 hover:text-slate-700'}`}
>Future $</button>
<Tooltip content="Select whether your spending goal is measured in today's purchasing power (adjusted for inflation) or future nominal dollars." className="ml-1 mt-1.5">
<div className="w-4 h-4" />
</Tooltip>
</div>
</div>
<div className="relative">
Expand All @@ -203,12 +219,15 @@ const InputSection: React.FC<InputSectionProps> = ({ profile, setProfile, onRest
{[
{ label: 'Traditional IRA / 401k', key: 'traditionalIRA' as const },
{ label: 'Roth IRA / 401k', key: 'rothIRA' as const },
{ label: 'Roth Carry/Basis', key: 'rothBasis' as const },
{ label: 'Roth Carry/Basis', key: 'rothBasis' as const, tooltip: 'Your total accumulated contributions to Roth accounts. This basis can be withdrawn tax-free and penalty-free at any time, making it a key liquidity source before age 59½.' },
{ label: 'Taxable Brokerage', key: 'brokerage' as const },
{ label: 'HSA', key: 'hsa' as const },
{ label: 'HSA', key: 'hsa' as const, tooltip: 'Health Savings Account balance. Funds can be withdrawn tax-free for qualified medical expenses.' },
].map((item) => (
<div key={item.key}>
<label htmlFor={`asset-${item.key}`} className={labelClass}>{item.label}</label>
<label htmlFor={`asset-${item.key}`} className={labelClass}>
{item.label}
{item.tooltip && <Tooltip content={item.tooltip} className="ml-1" />}
</label>
<div className="relative">
<span className={iconClass}>$</span>
<FormattedNumberInput
Expand Down
12 changes: 7 additions & 5 deletions src/components/features/LongevityAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { LongevityResult, UserProfile } from '../../types';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, Legend } from 'recharts';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer, BarChart, Bar, Legend } from 'recharts';
import Tooltip from '../common/Tooltip';
import { AlertTriangle, CheckCircle, HelpCircle } from 'lucide-react';

interface LongevityAnalysisProps {
Expand All @@ -26,8 +27,9 @@ const LongevityAnalysis: React.FC<LongevityAnalysisProps> = ({ longevity, profil
? 'bg-green-50 dark:bg-green-950/30 border-green-200 dark:border-green-900'
: 'bg-orange-50 dark:bg-orange-950/30 border-orange-200 dark:border-orange-900'
}`}>
<h3 className={`text-sm font-medium uppercase ${sustainable ? 'text-green-800 dark:text-green-400' : 'text-orange-800 dark:text-orange-400'}`}>
<h3 className={`text-sm font-medium uppercase flex items-center gap-2 ${sustainable ? 'text-green-800 dark:text-green-400' : 'text-orange-800 dark:text-orange-400'}`}>
Initial Withdrawal Rate
<Tooltip content="The percentage of your total portfolio withdrawn in the first year of retirement to cover expenses and taxes. Lower is safer." />
</h3>
<div className="flex items-baseline gap-2 mt-1">
<span className="text-3xl font-bold text-slate-900 dark:text-white">{(initialWithdrawalRate * 100).toFixed(1)}%</span>
Expand Down Expand Up @@ -96,7 +98,7 @@ const LongevityAnalysis: React.FC<LongevityAnalysisProps> = ({ longevity, profil
width={80}
stroke={axisColor}
/>
<Tooltip
<RechartsTooltip
content={({ active, payload, label }) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
Expand Down Expand Up @@ -219,7 +221,7 @@ const LongevityAnalysis: React.FC<LongevityAnalysisProps> = ({ longevity, profil
width={80}
stroke={axisColor}
/>
<Tooltip
<RechartsTooltip
cursor={{ fill: isDarkMode ? '#1e293b' : '#f1f5f9', opacity: 0.5 }}
content={({ active, payload, label }) => {
if (active && payload && payload.length) {
Expand Down Expand Up @@ -284,7 +286,7 @@ const LongevityAnalysis: React.FC<LongevityAnalysisProps> = ({ longevity, profil
{/* RMD indicator (age 73+) */}
{data.rmdAmount > 0 && (
<div className="flex justify-between items-center text-orange-600 dark:text-orange-400 text-[10px] italic">
<span>(RMD Required):</span>
<span className="flex items-center gap-1">(RMD Required): <Tooltip content="Required Minimum Distribution: The minimum amount the IRS requires you to withdraw from tax-deferred accounts annually." /></span>
<span className="font-mono">${Math.round(data.rmdAmount).toLocaleString()}</span>
</div>
)}
Expand Down
11 changes: 6 additions & 5 deletions src/components/features/StrategyResults.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState, useEffect } from 'react';
import { StrategyResult, UserProfile } from '../../types';
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Legend, CartesianGrid } from 'recharts';
import { PieChart, Pie, Cell, Tooltip as RechartsTooltip, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Legend, CartesianGrid } from 'recharts';
import { AlertCircle, CheckCircle, TrendingUp, Sparkles, MessageSquare, RefreshCw, ShieldAlert, Lock, Wallet, Info, AlertTriangle, ArrowRightLeft } from 'lucide-react';
import { getGeminiAdvice } from '../../services/geminiService';
import Tooltip from '../common/Tooltip';

interface StrategyResultsProps {
result: StrategyResult;
Expand Down Expand Up @@ -214,7 +215,7 @@ const StrategyResults: React.FC<StrategyResultsProps> = ({ result, profile, isDa
<p className="font-bold text-slate-900 dark:text-white">{formatCurrency(result.taxableSocialSecurity)}</p>
</div>
<div className="p-2 bg-slate-50 dark:bg-slate-800 rounded">
<span className="text-slate-500">Provisional Income</span>
<span className="text-slate-500 flex items-center gap-1">Provisional Income <Tooltip content="IRS formula used to determine how much of your Social Security benefits are subject to federal income tax." /></span>
<p className="font-bold text-slate-900 dark:text-white">{formatCurrency(result.provisionalIncome)}</p>
</div>
<div className="p-2 bg-slate-50 dark:bg-slate-800 rounded">
Expand Down Expand Up @@ -248,7 +249,7 @@ const StrategyResults: React.FC<StrategyResultsProps> = ({ result, profile, isDa
</p>
</div>
<div className="p-2 bg-slate-50 dark:bg-slate-800 rounded">
<span className="text-slate-500 text-[10px]">Effective Marginal Rate</span>
<span className="text-slate-500 text-[10px] flex items-center gap-1">Effective Marginal Rate <Tooltip content="The actual tax rate paid on your last dollar of income, accounting for hidden taxes like the Social Security tax torpedo." /></span>
<p className={`font-bold ${result.rothConversionDetail.effectiveMarginalRate > 0.30
? 'text-red-600 dark:text-red-400'
: 'text-slate-900 dark:text-white'
Expand Down Expand Up @@ -310,7 +311,7 @@ const StrategyResults: React.FC<StrategyResultsProps> = ({ result, profile, isDa
<div className="text-right">
{c.headroom > 0 && <span className="text-slate-600 dark:text-slate-400">Headroom: {formatCurrency(c.headroom)}</span>}
{c.annualCost && c.annualCost > 0 && (
<p className="text-red-500 text-[10px]">IRMAA: {formatCurrency(c.annualCost)}/yr if crossed</p>
<p className="text-red-500 text-[10px] flex items-center gap-1 justify-end">IRMAA: {formatCurrency(c.annualCost)}/yr if crossed <Tooltip content="Income-Related Monthly Adjustment Amount: A surcharge added to your Medicare Part B and Part D premiums if your income is too high." /></p>
)}
</div>
</div>
Expand Down Expand Up @@ -340,7 +341,7 @@ const StrategyResults: React.FC<StrategyResultsProps> = ({ result, profile, isDa
stroke={axisColor}
tickFormatter={(value) => value.length > 15 ? value.substring(0, 12) + '...' : value}
/>
<Tooltip
<RechartsTooltip
formatter={(value: number) => formatCurrency(value)}
contentStyle={{ backgroundColor: tooltipBg, borderColor: gridColor, color: tooltipText, fontSize: '12px' }}
/>
Expand Down
3 changes: 1 addition & 2 deletions src/components/features/wizard/Steps/Step8Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ const Step8Results: React.FC<CheckProps> = ({ data, onComplete, onRestart }) =>
// at the start of retirement to run the withdrawal strategy correctly.
const projectedAssets = projectAssets(
baseProfile.assets,
baseProfile.contributions.traditionalIRA + baseProfile.contributions.rothIRA + baseProfile.contributions.brokerage, // Total annual contrib
data.contributionAllocation || { taxDeferred: 50, taxable: 30, taxExempt: 20 }, // Allocation %
baseProfile.contributions,
baseProfile.baseAge, // Current Age
baseProfile.age, // Retirement Age
baseProfile.assumptions
Expand Down