@retiregolden/planner-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +181 -0
- package/package.json +77 -0
- package/src/App.tsx +246 -0
- package/src/RouteErrorBoundary.tsx +45 -0
- package/src/assets/hero.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/vite.svg +1 -0
- package/src/data/fedInvestClient.ts +113 -0
- package/src/data/localStore.ts +42 -0
- package/src/data/planOrigin.ts +24 -0
- package/src/data/planStore.ts +165 -0
- package/src/data/v2Backup.ts +101 -0
- package/src/import/ImportPage.tsx +347 -0
- package/src/import/ReviewChecklistView.tsx +38 -0
- package/src/import/brokerCsv.ts +395 -0
- package/src/import/csv.ts +133 -0
- package/src/import/genericCsv.ts +224 -0
- package/src/import/projectionLab.ts +350 -0
- package/src/import/reviewChecklist.ts +33 -0
- package/src/import/tenForty.ts +275 -0
- package/src/index.css +630 -0
- package/src/index.ts +16 -0
- package/src/learn/ArticleBody.tsx +78 -0
- package/src/learn/ArticlePage.tsx +57 -0
- package/src/learn/GlossaryPage.tsx +33 -0
- package/src/learn/LearnAboutScreen.tsx +41 -0
- package/src/learn/LearnCards.tsx +41 -0
- package/src/learn/LearnLink.tsx +91 -0
- package/src/learn/LearningCenterPage.tsx +114 -0
- package/src/learn/SourcesPage.tsx +98 -0
- package/src/learn/components/ArticleFigure.tsx +34 -0
- package/src/learn/components/ArticleShell.tsx +86 -0
- package/src/learn/components/ComparisonTable.tsx +42 -0
- package/src/learn/components/FormulaBlock.tsx +34 -0
- package/src/learn/components/PurchasingPowerChart.tsx +41 -0
- package/src/learn/components/RelatedArticles.tsx +27 -0
- package/src/learn/components/ScenarioCard.tsx +24 -0
- package/src/learn/components/SourceList.tsx +23 -0
- package/src/learn/components/charts.tsx +21 -0
- package/src/learn/content/about-retiregolden.ts +100 -0
- package/src/learn/content/aca-premium-tax-credits-and-magi.ts +103 -0
- package/src/learn/content/account-types-overview.ts +106 -0
- package/src/learn/content/after-tax-estate.ts +111 -0
- package/src/learn/content/agi-magi-and-taxable-income.ts +112 -0
- package/src/learn/content/appealing-irmaa-ssa-44.ts +95 -0
- package/src/learn/content/assumption-general-inflation.ts +82 -0
- package/src/learn/content/assumption-healthcare-inflation.ts +85 -0
- package/src/learn/content/assumption-heir-tax-rate.ts +79 -0
- package/src/learn/content/assumption-investment-returns.ts +90 -0
- package/src/learn/content/assumption-longevity-planning-age.ts +78 -0
- package/src/learn/content/assumption-recent-magi.ts +83 -0
- package/src/learn/content/assumption-social-security-cola.ts +89 -0
- package/src/learn/content/assumption-social-security-trust-fund.ts +83 -0
- package/src/learn/content/assumption-state-tax-override.ts +79 -0
- package/src/learn/content/beneficiaries-and-account-titling.ts +99 -0
- package/src/learn/content/break-even-useful-lens.ts +94 -0
- package/src/learn/content/building-a-retirement-spending-budget.ts +100 -0
- package/src/learn/content/cola-and-inflation-protection.ts +102 -0
- package/src/learn/content/divorced-spousal-and-survivor-records.ts +104 -0
- package/src/learn/content/dynamic-spending-guardrails.ts +90 -0
- package/src/learn/content/earnings-test-before-fra.ts +100 -0
- package/src/learn/content/employer-match-and-contribution-order.ts +104 -0
- package/src/learn/content/examplePlanArticles.ts +525 -0
- package/src/learn/content/fees-expense-ratios-and-compounding-drag.ts +98 -0
- package/src/learn/content/fi-number-and-four-percent-rule.ts +64 -0
- package/src/learn/content/filling-a-tax-bracket-with-roth-conversions.ts +98 -0
- package/src/learn/content/funded-ratio.ts +70 -0
- package/src/learn/content/healthcare-after-65.ts +103 -0
- package/src/learn/content/healthcare-before-65.ts +104 -0
- package/src/learn/content/historical-vs-random-return-models.ts +101 -0
- package/src/learn/content/how-assumptions-change-the-answer.ts +105 -0
- package/src/learn/content/how-much-can-i-spend.ts +105 -0
- package/src/learn/content/how-social-security-is-taxed.ts +95 -0
- package/src/learn/content/how-the-optimizer-thinks.ts +102 -0
- package/src/learn/content/how-the-optimizer-values-after-tax-estate.ts +97 -0
- package/src/learn/content/how-to-model-accumulation.ts +67 -0
- package/src/learn/content/how-to-read-a-retirement-projection.ts +115 -0
- package/src/learn/content/hsas-and-qualified-medical-expenses.ts +108 -0
- package/src/learn/content/hsas-as-retirement-accounts.ts +101 -0
- package/src/learn/content/inflation-risk.ts +98 -0
- package/src/learn/content/inherited-ira-10-year-rule.ts +105 -0
- package/src/learn/content/insurance-in-your-retirement-plan.ts +103 -0
- package/src/learn/content/irmaa-two-year-lookback.ts +99 -0
- package/src/learn/content/long-term-care-costs-and-insurance.ts +103 -0
- package/src/learn/content/long-term-care-insurance-as-risk-transfer.ts +98 -0
- package/src/learn/content/longevity-risk.ts +99 -0
- package/src/learn/content/marginal-vs-effective-tax-rate.ts +98 -0
- package/src/learn/content/medicare-part-b-vs-part-d-irmaa.ts +102 -0
- package/src/learn/content/mortality-weighted-social-security.ts +113 -0
- package/src/learn/content/moving-to-retiregolden.ts +86 -0
- package/src/learn/content/niit-high-income-investment-tax.ts +98 -0
- package/src/learn/content/ordinary-income-vs-capital-gains.ts +103 -0
- package/src/learn/content/paying-conversion-taxes-taxable-vs-ira.ts +102 -0
- package/src/learn/content/pensions-and-annuities.ts +101 -0
- package/src/learn/content/permanent-life-insurance-in-a-plan.ts +106 -0
- package/src/learn/content/pia-aime-and-bend-points.ts +103 -0
- package/src/learn/content/planner-overview.ts +106 -0
- package/src/learn/content/planning-for-couples-and-survivor-years.ts +108 -0
- package/src/learn/content/privacy-what-stays-in-your-browser.ts +99 -0
- package/src/learn/content/qcds-qualified-charitable-distributions.ts +101 -0
- package/src/learn/content/reading-the-results-page.ts +96 -0
- package/src/learn/content/reading-the-social-security-analysis-page.ts +106 -0
- package/src/learn/content/real-estate-home-equity-and-debt.ts +100 -0
- package/src/learn/content/reports-csv-exports-and-sharing.ts +101 -0
- package/src/learn/content/risk-based-guardrails.ts +100 -0
- package/src/learn/content/rmds-required-minimum-distributions.ts +100 -0
- package/src/learn/content/roth-conversion-basics.ts +104 -0
- package/src/learn/content/rsus-and-espp.ts +101 -0
- package/src/learn/content/rule-of-55-and-72t.ts +107 -0
- package/src/learn/content/savings-rate-biggest-lever.ts +66 -0
- package/src/learn/content/seed-your-plan-from-your-tax-return.ts +93 -0
- package/src/learn/content/sensitivity-testing-what-changes-the-answer.ts +104 -0
- package/src/learn/content/sequence-of-returns-risk.ts +98 -0
- package/src/learn/content/social-security-bridge.ts +67 -0
- package/src/learn/content/social-security-claiming-age-basics.ts +113 -0
- package/src/learn/content/social-security-taxes-vs-benefits.ts +76 -0
- package/src/learn/content/spending-profiles-and-the-retirement-smile.ts +92 -0
- package/src/learn/content/spousal-and-survivor-benefits.ts +120 -0
- package/src/learn/content/ssdi-and-retirement-planning.ts +72 -0
- package/src/learn/content/standard-deduction-senior-deduction-and-itemizing.ts +97 -0
- package/src/learn/content/state-income-taxes-in-retirement.ts +97 -0
- package/src/learn/content/step-up-in-basis.ts +102 -0
- package/src/learn/content/survivor-planning-for-couples.ts +110 -0
- package/src/learn/content/survivor-spending-in-couple-plans.ts +98 -0
- package/src/learn/content/tax-cliffs-and-bracket-edges.ts +105 -0
- package/src/learn/content/tax-loss-and-gain-harvesting.ts +99 -0
- package/src/learn/content/taxable-brokerage-basis-and-capital-gains.ts +99 -0
- package/src/learn/content/three-big-questions-spending-time-risk.ts +103 -0
- package/src/learn/content/tips-ladders.ts +92 -0
- package/src/learn/content/todays-dollars-vs-future-dollars.ts +107 -0
- package/src/learn/content/traditional-vs-roth-contributions.ts +113 -0
- package/src/learn/content/troubleshooting-surprising-results.ts +105 -0
- package/src/learn/content/trust-fund-haircut-scenarios.ts +101 -0
- package/src/learn/content/understanding-monte-carlo-success-rate.ts +118 -0
- package/src/learn/content/understanding-your-plan-assumptions.ts +134 -0
- package/src/learn/content/using-assumptions-and-provenance.ts +98 -0
- package/src/learn/content/using-scenarios-to-compare-choices.ts +99 -0
- package/src/learn/content/what-changes-when-you-move-states.ts +141 -0
- package/src/learn/content/what-is-fire.ts +65 -0
- package/src/learn/content/what-monte-carlo-proves.ts +98 -0
- package/src/learn/content/what-retiregolden-models.ts +103 -0
- package/src/learn/content/what-retirement-healthcare-really-costs.ts +117 -0
- package/src/learn/content/why-95-percent-is-not-a-guarantee.ts +98 -0
- package/src/learn/content/why-roth-conversions-raise-other-costs.ts +106 -0
- package/src/learn/content/why-small-tax-cliffs-can-matter.ts +109 -0
- package/src/learn/content/widows-penalty-and-survivor-brackets.ts +106 -0
- package/src/learn/content/withdrawal-order-basics.ts +105 -0
- package/src/learn/glossary.ts +191 -0
- package/src/learn/inlineMarkdown.tsx +54 -0
- package/src/learn/learn.css +537 -0
- package/src/learn/learningRegistry.ts +502 -0
- package/src/longevity/LongevityResults.tsx +85 -0
- package/src/longevity/LongevityWizard.tsx +305 -0
- package/src/longevity/constants.ts +15 -0
- package/src/longevity/factors.ts +125 -0
- package/src/longevity/model.ts +31 -0
- package/src/longevity/persistedGuard.ts +129 -0
- package/src/longevity/storage.ts +40 -0
- package/src/mc/messages.ts +118 -0
- package/src/mc/monteCarlo.worker.ts +44 -0
- package/src/mc/pool.ts +267 -0
- package/src/mc/runRequest.ts +125 -0
- package/src/optimize/messages.ts +84 -0
- package/src/optimize/optimize.worker.ts +29 -0
- package/src/optimize/runOptimize.ts +92 -0
- package/src/optimize/runSpendingSolve.ts +47 -0
- package/src/optimize/runner.ts +21 -0
- package/src/optimize/spendingMessages.ts +44 -0
- package/src/optimize/spendingRunner.ts +21 -0
- package/src/optimize/spendingSolve.worker.ts +18 -0
- package/src/planner/AssumptionsCardPage.tsx +136 -0
- package/src/planner/BucketLensCard.tsx +114 -0
- package/src/planner/ComparePlansPage.tsx +219 -0
- package/src/planner/DisclaimerPage.tsx +88 -0
- package/src/planner/HowTestedPage.tsx +159 -0
- package/src/planner/LiveStatus.tsx +15 -0
- package/src/planner/LongevityModal.tsx +55 -0
- package/src/planner/Modal.tsx +97 -0
- package/src/planner/MonteCarloPage.tsx +907 -0
- package/src/planner/OptimizePage.tsx +611 -0
- package/src/planner/PlanContext.tsx +198 -0
- package/src/planner/PlanPickerPage.tsx +124 -0
- package/src/planner/PlanWorkspace.tsx +290 -0
- package/src/planner/ProvenancePanel.tsx +45 -0
- package/src/planner/RelocationComparePage.tsx +485 -0
- package/src/planner/ReportPage.tsx +375 -0
- package/src/planner/ResultsPage.tsx +817 -0
- package/src/planner/ScenariosPage.tsx +285 -0
- package/src/planner/SocialSecuritySection.tsx +556 -0
- package/src/planner/SpendingSolverPage.tsx +512 -0
- package/src/planner/SsAnalysisPage.tsx +1134 -0
- package/src/planner/SurvivalPercentileModal.tsx +161 -0
- package/src/planner/SurvivorTransitionPage.tsx +286 -0
- package/src/planner/assumptionsExport.ts +371 -0
- package/src/planner/bucketLens.ts +89 -0
- package/src/planner/chartFrame.ts +8 -0
- package/src/planner/chartStyle.ts +11 -0
- package/src/planner/dialogViews.tsx +184 -0
- package/src/planner/dialogs.tsx +133 -0
- package/src/planner/examples/ExampleLibrary.tsx +189 -0
- package/src/planner/examples/ExamplePreviewBanner.tsx +55 -0
- package/src/planner/examples/ExamplesPage.tsx +25 -0
- package/src/planner/examples/OpenExampleButton.tsx +61 -0
- package/src/planner/examples/buildAggressiveSaver.ts +102 -0
- package/src/planner/examples/buildAnnuityEstate.ts +137 -0
- package/src/planner/examples/buildBaristaFire.ts +115 -0
- package/src/planner/examples/buildBracketFillRoth.ts +65 -0
- package/src/planner/examples/buildBridgeEarlyRetirement.ts +94 -0
- package/src/planner/examples/buildBrokerageNoHsa.ts +109 -0
- package/src/planner/examples/buildCoastFire.ts +88 -0
- package/src/planner/examples/buildContext.ts +20 -0
- package/src/planner/examples/buildEarlyCareerMatch.ts +93 -0
- package/src/planner/examples/buildEarlyRetireeAca.ts +61 -0
- package/src/planner/examples/buildExampleCouple.ts +103 -0
- package/src/planner/examples/buildFixedTargetSpending.ts +74 -0
- package/src/planner/examples/buildGlidepathAllocation.ts +131 -0
- package/src/planner/examples/buildGuardrailsFlex.ts +120 -0
- package/src/planner/examples/buildHsaPropertyDepth.ts +109 -0
- package/src/planner/examples/buildHsaStealthRetirement.ts +97 -0
- package/src/planner/examples/buildLeanFatFire.ts +109 -0
- package/src/planner/examples/buildLtcShock.ts +62 -0
- package/src/planner/examples/buildMovingStateTax.ts +53 -0
- package/src/planner/examples/buildNoAnnuityBrokerage.ts +92 -0
- package/src/planner/examples/buildRmdIrmaa.ts +55 -0
- package/src/planner/examples/buildSalaryGrowthEscalation.ts +96 -0
- package/src/planner/examples/buildStaticAllocationControl.ts +96 -0
- package/src/planner/examples/buildSurvivorYears.ts +62 -0
- package/src/planner/examples/buildUnderSavedSingle.ts +51 -0
- package/src/planner/examples/exampleCopy.ts +23 -0
- package/src/planner/examples/loadExample.ts +90 -0
- package/src/planner/examples/registry.ts +313 -0
- package/src/planner/explainPanels.tsx +233 -0
- package/src/planner/fields.tsx +381 -0
- package/src/planner/format.ts +33 -0
- package/src/planner/home/DataAndPrivacyCard.tsx +56 -0
- package/src/planner/home/GettingStartedPaths.tsx +46 -0
- package/src/planner/home/GettingStartedReopener.tsx +32 -0
- package/src/planner/home/StartHereLinks.tsx +22 -0
- package/src/planner/home/WelcomeHero.tsx +39 -0
- package/src/planner/home/YourPlans.tsx +72 -0
- package/src/planner/home/importErrorMessage.ts +22 -0
- package/src/planner/home/startHereSlugs.ts +7 -0
- package/src/planner/home/useHomeData.ts +190 -0
- package/src/planner/home/useHomeMode.ts +47 -0
- package/src/planner/householdActions.ts +22 -0
- package/src/planner/insights/InsightCardView.tsx +340 -0
- package/src/planner/insights/InsightsPage.tsx +204 -0
- package/src/planner/insights/categoryLabels.ts +11 -0
- package/src/planner/learnLinks.ts +85 -0
- package/src/planner/marketModelPicker.ts +172 -0
- package/src/planner/optimizePageChart.ts +40 -0
- package/src/planner/optimizePageClaim.ts +64 -0
- package/src/planner/planCompleteness.ts +27 -0
- package/src/planner/planContextCore.ts +26 -0
- package/src/planner/planner.css +2304 -0
- package/src/planner/provenanceLinks.ts +25 -0
- package/src/planner/sections/AccountFields.tsx +872 -0
- package/src/planner/sections/AccountsSection.tsx +89 -0
- package/src/planner/sections/AllocationPanel.tsx +261 -0
- package/src/planner/sections/AssumptionsSection.tsx +256 -0
- package/src/planner/sections/HouseholdSection.tsx +243 -0
- package/src/planner/sections/IncomeFloorSection.tsx +418 -0
- package/src/planner/sections/IncomeSection.tsx +170 -0
- package/src/planner/sections/InsuranceSection.tsx +362 -0
- package/src/planner/sections/SpendingSection.tsx +904 -0
- package/src/planner/sections/StrategySection.tsx +349 -0
- package/src/planner/sections/UpdateBalancesPanel.tsx +182 -0
- package/src/planner/sections/sectionHelpers.ts +48 -0
- package/src/planner/sections/shared.tsx +15 -0
- package/src/planner/sections.tsx +15 -0
- package/src/planner/ssAnalysis.ts +325 -0
- package/src/planner/successBand.ts +20 -0
- package/src/planner/survivorAnalysis.ts +277 -0
- package/src/planner/usStates.ts +19 -0
- package/src/planner/useMcSuccessRate.ts +77 -0
- package/src/planner/useProjection.ts +63 -0
- package/src/relocation/messages.ts +21 -0
- package/src/relocation/relocation.worker.ts +18 -0
- package/src/relocation/runRelocation.ts +17 -0
- package/src/relocation/runner.ts +22 -0
- package/src/report/brandingContext.ts +15 -0
- package/src/report/downloadReport.ts +34 -0
- package/src/report/reportHtml.ts +547 -0
- package/src/routes/LearnRoutes.tsx +46 -0
- package/src/routes/PlanRoutes.tsx +55 -0
- package/src/routes/RouteFallback.tsx +9 -0
- package/src/socialSecurity/breakEven.ts +107 -0
- package/src/socialSecurity/expectedPv.ts +164 -0
- package/src/socialSecurity/explain.ts +92 -0
- package/src/socialSecurity/ficaReturn.ts +81 -0
- package/src/socialSecurity/persistedSsGuard.ts +138 -0
- package/src/socialSecurity/ssFormUtils.ts +48 -0
- package/src/socialSecurity/ssaStatementXml.ts +156 -0
- package/src/socialSecurity/storage.ts +69 -0
- package/src/socialSecurity/survivorSwitching.ts +153 -0
- package/src/testSupport/samplePlan.ts +2 -0
- package/src/workers/run.ts +45 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import & migration wizard at /import (onboarding-import-and-migration).
|
|
3
|
+
*
|
|
4
|
+
* Four guided paths — ProjectionLab JSON, broker positions CSV, generic
|
|
5
|
+
* spreadsheet/RPM CSV with column mapping, and the 1040 guided seed — all
|
|
6
|
+
* producing a draft plan through the same validated route as backup import,
|
|
7
|
+
* with the shared review checklist before anything is saved. Everything is
|
|
8
|
+
* parsed in the browser; no file leaves the device.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useRef, useState } from 'react'
|
|
12
|
+
import { Link, useNavigate } from 'react-router-dom'
|
|
13
|
+
|
|
14
|
+
import { savePlan } from '../data/planStore'
|
|
15
|
+
import type { Plan } from '@retiregolden/engine/model/plan'
|
|
16
|
+
import { DateField, MoneyField, SelectField } from '../planner/fields'
|
|
17
|
+
import { US_STATES } from '../planner/usStates'
|
|
18
|
+
import { parseBrokerPositionsCsv, draftPlanFromBrokerAccounts, BROKER_LABEL } from './brokerCsv'
|
|
19
|
+
import {
|
|
20
|
+
analyzeGenericCsv,
|
|
21
|
+
COLUMN_ROLE_LABEL,
|
|
22
|
+
draftPlanFromGenericCsv,
|
|
23
|
+
type ColumnRole,
|
|
24
|
+
type GenericCsvAnalysis,
|
|
25
|
+
} from './genericCsv'
|
|
26
|
+
import { mapProjectionLabExport } from './projectionLab'
|
|
27
|
+
import type { ImportReviewItem } from './reviewChecklist'
|
|
28
|
+
import { ReviewChecklist } from './ReviewChecklistView'
|
|
29
|
+
import { seedPlanFromTenForty, type TenFortyInputs } from './tenForty'
|
|
30
|
+
|
|
31
|
+
type SourceId = 'projectionlab' | 'broker' | 'generic' | 'tenforty'
|
|
32
|
+
|
|
33
|
+
interface Draft {
|
|
34
|
+
plan: Plan
|
|
35
|
+
review: ImportReviewItem[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SOURCES: Array<{ id: SourceId; title: string; desc: string }> = [
|
|
39
|
+
{
|
|
40
|
+
id: 'broker',
|
|
41
|
+
title: 'Broker CSV (Schwab, Fidelity, Vanguard)',
|
|
42
|
+
desc: 'Download your positions/holdings CSV from your broker and turn account balances into a draft plan.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'projectionlab',
|
|
46
|
+
title: 'ProjectionLab export',
|
|
47
|
+
desc: 'Bring accounts, income, and spending over from a ProjectionLab JSON data export.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'generic',
|
|
51
|
+
title: 'Spreadsheet / RPM CSV',
|
|
52
|
+
desc: 'Any sheet with one row per account — including the Bogleheads Retiree Portfolio Model — via column mapping.',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'tenforty',
|
|
56
|
+
title: 'Your tax return (Form 1040)',
|
|
57
|
+
desc: 'Type about a dozen line values off last year’s return to seed income, filing, and bracket context.',
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const ROLE_OPTIONS = (Object.keys(COLUMN_ROLE_LABEL) as ColumnRole[]).map((value) => ({
|
|
62
|
+
value,
|
|
63
|
+
label: COLUMN_ROLE_LABEL[value],
|
|
64
|
+
}))
|
|
65
|
+
|
|
66
|
+
const EMPTY_1040: TenFortyInputs = {
|
|
67
|
+
filingStatus: 'single',
|
|
68
|
+
state: 'KY',
|
|
69
|
+
primaryDob: '1970-01-01',
|
|
70
|
+
spouseDob: undefined,
|
|
71
|
+
wages: 0,
|
|
72
|
+
taxExemptInterest: 0,
|
|
73
|
+
taxableInterest: 0,
|
|
74
|
+
qualifiedDividends: 0,
|
|
75
|
+
ordinaryDividends: 0,
|
|
76
|
+
iraDistributions: 0,
|
|
77
|
+
pensionsAndAnnuities: 0,
|
|
78
|
+
socialSecurityBenefits: 0,
|
|
79
|
+
capitalGain: 0,
|
|
80
|
+
agi: 0,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function ImportPage() {
|
|
84
|
+
const navigate = useNavigate()
|
|
85
|
+
const [source, setSource] = useState<SourceId | null>(null)
|
|
86
|
+
const [error, setError] = useState<string | null>(null)
|
|
87
|
+
const [draft, setDraft] = useState<Draft | null>(null)
|
|
88
|
+
const [analysis, setAnalysis] = useState<GenericCsvAnalysis | null>(null)
|
|
89
|
+
const [roles, setRoles] = useState<ColumnRole[]>([])
|
|
90
|
+
const [tenForty, setTenForty] = useState<TenFortyInputs>(EMPTY_1040)
|
|
91
|
+
const fileInput = useRef<HTMLInputElement>(null)
|
|
92
|
+
|
|
93
|
+
const reset = () => {
|
|
94
|
+
setError(null)
|
|
95
|
+
setDraft(null)
|
|
96
|
+
setAnalysis(null)
|
|
97
|
+
setRoles([])
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const chooseSource = (id: SourceId) => {
|
|
101
|
+
reset()
|
|
102
|
+
setSource(id)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handleFile = async (file: File) => {
|
|
106
|
+
setError(null)
|
|
107
|
+
const text = await file.text()
|
|
108
|
+
if (source === 'projectionlab') {
|
|
109
|
+
const r = mapProjectionLabExport(text)
|
|
110
|
+
if (!r.ok) return setError(r.message)
|
|
111
|
+
setDraft({ plan: r.plan, review: r.review })
|
|
112
|
+
} else if (source === 'broker') {
|
|
113
|
+
const parsed = parseBrokerPositionsCsv(text)
|
|
114
|
+
if (!parsed.ok) return setError(parsed.message)
|
|
115
|
+
const drafted = draftPlanFromBrokerAccounts(parsed.broker, parsed.accounts)
|
|
116
|
+
if (!drafted.ok) return setError(drafted.message)
|
|
117
|
+
setDraft({
|
|
118
|
+
plan: drafted.plan,
|
|
119
|
+
review: [
|
|
120
|
+
{
|
|
121
|
+
status: 'mapped',
|
|
122
|
+
source: `${BROKER_LABEL[parsed.broker]} positions file`,
|
|
123
|
+
detail: `Recognized ${parsed.accounts.length} account${parsed.accounts.length === 1 ? '' : 's'}.`,
|
|
124
|
+
},
|
|
125
|
+
...parsed.review,
|
|
126
|
+
...drafted.review,
|
|
127
|
+
],
|
|
128
|
+
})
|
|
129
|
+
} else if (source === 'generic') {
|
|
130
|
+
const r = analyzeGenericCsv(text)
|
|
131
|
+
if (!r.ok) return setError(r.message)
|
|
132
|
+
setAnalysis(r.analysis)
|
|
133
|
+
setRoles(r.analysis.guessedRoles)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const buildGenericDraft = () => {
|
|
138
|
+
if (!analysis) return
|
|
139
|
+
setError(null)
|
|
140
|
+
const r = draftPlanFromGenericCsv(analysis, roles)
|
|
141
|
+
if (!r.ok) return setError(r.message)
|
|
142
|
+
setDraft({ plan: r.plan, review: r.review })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const buildTenFortyDraft = () => {
|
|
146
|
+
setError(null)
|
|
147
|
+
const r = seedPlanFromTenForty(tenForty)
|
|
148
|
+
if (!r.ok) return setError(r.message)
|
|
149
|
+
setDraft({ plan: r.plan, review: r.review })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const saveAndOpen = async () => {
|
|
153
|
+
if (!draft) return
|
|
154
|
+
const r = await savePlan(draft.plan)
|
|
155
|
+
if (r.ok) navigate(`/plan/${r.plan.id}`)
|
|
156
|
+
else setError(`Could not save the draft plan: ${r.issues.join('; ')}`)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const set1040 = (patch: Partial<TenFortyInputs>) => setTenForty((prev) => ({ ...prev, ...patch }))
|
|
160
|
+
|
|
161
|
+
const fileAccept = source === 'projectionlab' ? 'application/json,.json' : '.csv,text/csv'
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="import-page">
|
|
165
|
+
<h1>Import & migrate</h1>
|
|
166
|
+
<p className="card-hint">
|
|
167
|
+
Start a RetireGolden plan from files you already have. Every file is read entirely on this device —
|
|
168
|
+
nothing is uploaded anywhere — and you review exactly what mapped before anything is saved. Restoring
|
|
169
|
+
a RetireGolden backup lives on the <Link to="/">planner home</Link>.
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
{!source ? (
|
|
173
|
+
<div className="plan-grid home-paths-grid">
|
|
174
|
+
{SOURCES.map((s) => (
|
|
175
|
+
<button key={s.id} type="button" className="home-path-card plan-card" onClick={() => chooseSource(s.id)}>
|
|
176
|
+
<span className="home-path-card-title">{s.title}</span>
|
|
177
|
+
<span className="home-path-card-desc">{s.desc}</span>
|
|
178
|
+
</button>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
) : (
|
|
182
|
+
<div className="card">
|
|
183
|
+
<div className="item-row-head">
|
|
184
|
+
<h2>{SOURCES.find((s) => s.id === source)!.title}</h2>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
className="btn-ghost"
|
|
188
|
+
onClick={() => {
|
|
189
|
+
reset()
|
|
190
|
+
setSource(null)
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
Choose a different source
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{error ? (
|
|
198
|
+
<div className="callout callout--warn" role="alert">
|
|
199
|
+
{error}
|
|
200
|
+
</div>
|
|
201
|
+
) : null}
|
|
202
|
+
|
|
203
|
+
{draft ? (
|
|
204
|
+
<>
|
|
205
|
+
<p>
|
|
206
|
+
Draft plan <strong>{draft.plan.name}</strong>: {draft.plan.accounts.length} account
|
|
207
|
+
{draft.plan.accounts.length === 1 ? '' : 's'}, {draft.plan.incomes.length} income stream
|
|
208
|
+
{draft.plan.incomes.length === 1 ? '' : 's'}.
|
|
209
|
+
</p>
|
|
210
|
+
<ReviewChecklist items={draft.review} />
|
|
211
|
+
<div className="picker-actions">
|
|
212
|
+
<button type="button" className="btn btn-primary" onClick={() => void saveAndOpen()}>
|
|
213
|
+
Save draft & open in the planner
|
|
214
|
+
</button>
|
|
215
|
+
<button type="button" className="btn btn-secondary" onClick={reset}>
|
|
216
|
+
Start over
|
|
217
|
+
</button>
|
|
218
|
+
</div>
|
|
219
|
+
</>
|
|
220
|
+
) : source === 'tenforty' ? (
|
|
221
|
+
<>
|
|
222
|
+
<p className="card-hint">
|
|
223
|
+
Copy the values from last year's Form 1040. Zero is fine for any line that doesn't
|
|
224
|
+
apply. No PDF upload — you stay in control of what is entered.
|
|
225
|
+
</p>
|
|
226
|
+
<div className="form-grid">
|
|
227
|
+
<SelectField
|
|
228
|
+
label="Filing status (1040 header)"
|
|
229
|
+
value={tenForty.filingStatus}
|
|
230
|
+
options={[
|
|
231
|
+
{ value: 'single', label: 'Single' },
|
|
232
|
+
{ value: 'marriedFilingJointly', label: 'Married filing jointly' },
|
|
233
|
+
]}
|
|
234
|
+
onCommit={(v) =>
|
|
235
|
+
set1040({ filingStatus: v, spouseDob: v === 'marriedFilingJointly' ? (tenForty.spouseDob ?? '1970-01-01') : undefined })
|
|
236
|
+
}
|
|
237
|
+
/>
|
|
238
|
+
<SelectField label="State of residence" value={tenForty.state} options={US_STATES} onCommit={(v) => set1040({ state: v })} />
|
|
239
|
+
<DateField label="Your date of birth" help="Not on the 1040, but every projection needs it to anchor ages." value={tenForty.primaryDob} onCommit={(v) => set1040({ primaryDob: v })} />
|
|
240
|
+
{tenForty.filingStatus === 'marriedFilingJointly' ? (
|
|
241
|
+
<DateField label="Spouse's date of birth" value={tenForty.spouseDob ?? ''} onCommit={(v) => set1040({ spouseDob: v })} />
|
|
242
|
+
) : null}
|
|
243
|
+
<MoneyField label="Line 1a — wages" value={tenForty.wages} onCommit={(v) => set1040({ wages: v ?? 0 })} />
|
|
244
|
+
<MoneyField label="Line 2a — tax-exempt interest" value={tenForty.taxExemptInterest} onCommit={(v) => set1040({ taxExemptInterest: v ?? 0 })} />
|
|
245
|
+
<MoneyField label="Line 2b — taxable interest" value={tenForty.taxableInterest} onCommit={(v) => set1040({ taxableInterest: v ?? 0 })} />
|
|
246
|
+
<MoneyField label="Line 3a — qualified dividends" value={tenForty.qualifiedDividends} onCommit={(v) => set1040({ qualifiedDividends: v ?? 0 })} />
|
|
247
|
+
<MoneyField label="Line 3b — ordinary dividends" value={tenForty.ordinaryDividends} onCommit={(v) => set1040({ ordinaryDividends: v ?? 0 })} />
|
|
248
|
+
<MoneyField label="Line 4b — IRA distributions (taxable)" value={tenForty.iraDistributions} onCommit={(v) => set1040({ iraDistributions: v ?? 0 })} />
|
|
249
|
+
<MoneyField label="Line 5b — pensions & annuities (taxable)" value={tenForty.pensionsAndAnnuities} onCommit={(v) => set1040({ pensionsAndAnnuities: v ?? 0 })} />
|
|
250
|
+
<MoneyField label="Line 6a — Social Security benefits" value={tenForty.socialSecurityBenefits} onCommit={(v) => set1040({ socialSecurityBenefits: v ?? 0 })} />
|
|
251
|
+
<MoneyField label="Line 7 — capital gain or loss" help="Enter a loss as a negative number." value={tenForty.capitalGain} onCommit={(v) => set1040({ capitalGain: v ?? 0 })} />
|
|
252
|
+
<MoneyField label="Line 11 — adjusted gross income" value={tenForty.agi} onCommit={(v) => set1040({ agi: v ?? 0 })} />
|
|
253
|
+
</div>
|
|
254
|
+
<div className="picker-actions">
|
|
255
|
+
<button type="button" className="btn btn-primary" onClick={buildTenFortyDraft}>
|
|
256
|
+
Build my draft plan
|
|
257
|
+
</button>
|
|
258
|
+
</div>
|
|
259
|
+
</>
|
|
260
|
+
) : analysis ? (
|
|
261
|
+
<>
|
|
262
|
+
<p className="card-hint">
|
|
263
|
+
Tell RetireGolden what each column means. The guesses below come from your header row; fix any
|
|
264
|
+
that are wrong, then continue. {analysis.dataRows.length} data row
|
|
265
|
+
{analysis.dataRows.length === 1 ? '' : 's'} found.
|
|
266
|
+
</p>
|
|
267
|
+
<div className="year-table-wrap">
|
|
268
|
+
<table className="year-table">
|
|
269
|
+
<thead>
|
|
270
|
+
<tr>
|
|
271
|
+
{analysis.header.map((h, i) => (
|
|
272
|
+
<th key={i} scope="col">
|
|
273
|
+
{h || `Column ${i + 1}`}
|
|
274
|
+
</th>
|
|
275
|
+
))}
|
|
276
|
+
</tr>
|
|
277
|
+
<tr>
|
|
278
|
+
{analysis.header.map((_, i) => (
|
|
279
|
+
<td key={i}>
|
|
280
|
+
<select
|
|
281
|
+
aria-label={`Role for column ${analysis.header[i] || i + 1}`}
|
|
282
|
+
value={roles[i] ?? 'ignore'}
|
|
283
|
+
onChange={(e) =>
|
|
284
|
+
setRoles((prev) => prev.map((r, j) => (j === i ? (e.target.value as ColumnRole) : r)))
|
|
285
|
+
}
|
|
286
|
+
>
|
|
287
|
+
{ROLE_OPTIONS.map((o) => (
|
|
288
|
+
<option key={o.value} value={o.value}>
|
|
289
|
+
{o.label}
|
|
290
|
+
</option>
|
|
291
|
+
))}
|
|
292
|
+
</select>
|
|
293
|
+
</td>
|
|
294
|
+
))}
|
|
295
|
+
</tr>
|
|
296
|
+
</thead>
|
|
297
|
+
<tbody>
|
|
298
|
+
{analysis.dataRows.slice(0, 5).map((row, r) => (
|
|
299
|
+
<tr key={r}>
|
|
300
|
+
{analysis.header.map((_, c) => (
|
|
301
|
+
<td key={c}>{row[c] ?? ''}</td>
|
|
302
|
+
))}
|
|
303
|
+
</tr>
|
|
304
|
+
))}
|
|
305
|
+
</tbody>
|
|
306
|
+
</table>
|
|
307
|
+
</div>
|
|
308
|
+
<div className="picker-actions">
|
|
309
|
+
<button type="button" className="btn btn-primary" onClick={buildGenericDraft}>
|
|
310
|
+
Continue with these columns
|
|
311
|
+
</button>
|
|
312
|
+
</div>
|
|
313
|
+
</>
|
|
314
|
+
) : (
|
|
315
|
+
<>
|
|
316
|
+
<p className="card-hint">
|
|
317
|
+
{source === 'projectionlab'
|
|
318
|
+
? 'In ProjectionLab, export your data as JSON (Settings → Export Data), then choose the file here.'
|
|
319
|
+
: source === 'broker'
|
|
320
|
+
? 'Log in to your broker and download the positions/holdings CSV, then choose the file here. To update balances in an existing plan instead, use "Update balances from a broker CSV" on that plan’s Accounts screen.'
|
|
321
|
+
: 'Save your spreadsheet as CSV with a header row and one row per account, then choose the file here.'}
|
|
322
|
+
</p>
|
|
323
|
+
<div className="picker-actions">
|
|
324
|
+
<button type="button" className="btn btn-primary" onClick={() => fileInput.current?.click()}>
|
|
325
|
+
Choose file
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
<input
|
|
329
|
+
ref={fileInput}
|
|
330
|
+
type="file"
|
|
331
|
+
accept={fileAccept}
|
|
332
|
+
className="sr-only"
|
|
333
|
+
tabIndex={-1}
|
|
334
|
+
aria-hidden="true"
|
|
335
|
+
onChange={(e) => {
|
|
336
|
+
const f = e.target.files?.[0]
|
|
337
|
+
if (f) void handleFile(f)
|
|
338
|
+
e.target.value = ''
|
|
339
|
+
}}
|
|
340
|
+
/>
|
|
341
|
+
</>
|
|
342
|
+
)}
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
</div>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared review-checklist UI for every import path: what mapped, what was
|
|
3
|
+
* assumed, what did not import, and what was skipped — grouped with the
|
|
4
|
+
* items that need the user's attention first. Values render as inert text.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { countByStatus, IMPORT_STATUS_LABEL, IMPORT_STATUS_ORDER, type ImportReviewItem } from './reviewChecklist'
|
|
8
|
+
|
|
9
|
+
export function ReviewChecklist({ items }: { items: ImportReviewItem[] }) {
|
|
10
|
+
const counts = countByStatus(items)
|
|
11
|
+
return (
|
|
12
|
+
<div className="import-review">
|
|
13
|
+
<h3>Review what was imported</h3>
|
|
14
|
+
<p className="card-hint">
|
|
15
|
+
Nothing imports silently: every value placed in the draft plan, every assumption made for you, and
|
|
16
|
+
everything that could not be brought over is listed here. You can change all of it in the planner.
|
|
17
|
+
</p>
|
|
18
|
+
{IMPORT_STATUS_ORDER.map((status) => {
|
|
19
|
+
const group = items.filter((i) => i.status === status)
|
|
20
|
+
if (group.length === 0) return null
|
|
21
|
+
return (
|
|
22
|
+
<section key={status} className={`import-review-group import-review-group--${status}`}>
|
|
23
|
+
<h4>
|
|
24
|
+
{IMPORT_STATUS_LABEL[status]} <span className="muted">({counts[status]})</span>
|
|
25
|
+
</h4>
|
|
26
|
+
<ul>
|
|
27
|
+
{group.map((item, i) => (
|
|
28
|
+
<li key={`${status}-${i}`}>
|
|
29
|
+
<strong>{item.source}</strong> — {item.detail}
|
|
30
|
+
</li>
|
|
31
|
+
))}
|
|
32
|
+
</ul>
|
|
33
|
+
</section>
|
|
34
|
+
)
|
|
35
|
+
})}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|