diff --git a/src/components/CareerRoadmap.js b/src/components/CareerRoadmap.js
index 9f594db..8924e41 100644
--- a/src/components/CareerRoadmap.js
+++ b/src/components/CareerRoadmap.js
@@ -368,6 +368,7 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
const [drawerMilestone, setDrawerMilestone] = useState(null);
const [impactsById, setImpactsById] = useState({}); // id → [impacts]
const [addingNewMilestone, setAddingNewMilestone] = useState(false);
+ const [showMissingBanner, setShowMissingBanner] = useState(false);
// Config
@@ -573,16 +574,14 @@ useEffect(() => {
const dataReady = !!scenarioRow && !!financialProfile && collegeProfile !== null;
useEffect(() => {
+ if (!dataReady || !careerProfileId) return; // wait for all rows
- if (!dataReady || !careerProfileId) return;
+ /* run once per profile‑id ------------------------------------------------ */
+ if (modalGuard.current.checked) return;
+ modalGuard.current.checked = true;
- // one key per career profile
- const key = `modalChecked:${careerProfileId}`;
-
- // already checked in this browser session?
- if (sessionStorage.getItem(key) === '1') return;
-
- const status = (scenarioRow.college_enrollment_status || '').toLowerCase();
+ /* derive once, local to this effect -------------------------------------- */
+ const status = (scenarioRow?.college_enrollment_status || '').toLowerCase();
const requireCollege = ['currently_enrolled','prospective_student','deferred']
.includes(status);
@@ -591,18 +590,24 @@ useEffect(() => {
{ requireCollegeData: requireCollege }
);
- if (missing.length) setShowEditModal(true);
-
- sessionStorage.setItem(key, '1'); // remember for this tab
-
+ if (missing.length) {
+ /* if we arrived *directly* from onboarding we silently skip the banner
+ once, but we still want the Edit‑Scenario modal to open */
+ if (modalGuard.current.skip) {
+ setShowEditModal(true);
+ } else {
+ setShowMissingBanner(true);
+ }
+ }
}, [dataReady, scenarioRow, financialProfile, collegeProfile, careerProfileId]);
+
+
useEffect(() => {
if (
financialProfile &&
scenarioRow &&
- collegeProfile &&
- scenarioMilestones.length
+ collegeProfile
) {
buildProjection(scenarioMilestones); // uses the latest scenarioMilestones
}
@@ -949,7 +954,7 @@ useEffect(() => {
// 8) Build financial projection
async function buildProjection(milestones) {
if (!milestones?.length) return;
- const allMilestones = milestones;
+ const allMilestones = milestones || [];
try {
setScenarioMilestones(allMilestones);
@@ -1302,9 +1307,8 @@ const fetchMilestones = useCallback(async () => {
setScenarioRow={setScenarioRow}
careerProfileId={careerProfileId}
collegeProfile={collegeProfile}
- onMilestonesCreated={() => {
- /* refresh or reload logic here */
- }}
+ onMilestonesCreated={handleMilestonesCreated}
+
onAiRiskFetched={(riskData) => {
@@ -1447,6 +1451,28 @@ const fetchMilestones = useCallback(async () => {
*/}
{/* --- FINANCIAL PROJECTION SECTION -------------------------------- */}
+
+ {showMissingBanner && (
+
+
+ We need a few basics (income, expenses, etc.) before we can show a full
+ projection.
+
+
+
+
+ )}
+
Financial Projection
@@ -1600,8 +1626,8 @@ const fetchMilestones = useCallback(async () => {
milestone={milestoneForModal} /* ← edit mode */
fetchMilestones={fetchMilestones}
onClose={(didSave) => {
+ if (didSave) handleMilestonesCreated();
setMilestoneForModal(null);
- if (didSave) fetchMilestones();
}}
/>
)}
diff --git a/src/components/RetirementPlanner.js b/src/components/RetirementPlanner.js
index c6a105b..4a6dbbb 100644
--- a/src/components/RetirementPlanner.js
+++ b/src/components/RetirementPlanner.js
@@ -42,6 +42,7 @@ export default function RetirementPlanner () {
const isMobile = useIsMobile();
const { openRetire } = useContext(ChatCtx);
+
/* ----------------------- data loading -------------------------- */
const loadAll = useCallback(async () => {
try {
diff --git a/src/components/ScenarioContainer.js b/src/components/ScenarioContainer.js
index 7bedb4c..644501c 100644
--- a/src/components/ScenarioContainer.js
+++ b/src/components/ScenarioContainer.js
@@ -230,10 +230,7 @@ export default function ScenarioContainer({
};
// Gather milestoneImpacts
- let allImpacts = [];
- Object.keys(impactsByMilestone).forEach((mId) => {
- allImpacts = allImpacts.concat(impactsByMilestone[mId]);
- });
+ const allImpacts = Object.values(impactsByMilestone).flat(); // safe even if []
const simYears = parseInt(simulationYearsInput, 10) || 20;
const simYearsUI = Math.max(1, parseInt(simulationYearsInput, 10) || 20);
@@ -315,13 +312,20 @@ export default function ScenarioContainer({
surplusEmergencyAllocation: scenarioOverrides.surplusEmergencyAllocation,
surplusRetirementAllocation: scenarioOverrides.surplusRetirementAllocation,
additionalIncome: scenarioOverrides.additionalIncome,
- retirement_start_date: localScenario.retirement_start_date
- || localScenario.projected_end_date
- || null,
- desired_retirement_income_monthly: parseScenarioOverride(
- localScenario.desired_retirement_income_monthly,
- 0
- ),
+ retirement_start_date:
+ localScenario.retirement_start_date // user picked
+ || (localScenario.projected_end_date // often set for college scenarios
+ ? moment(localScenario.projected_end_date)
+ .startOf('month')
+ .add(1,'month') // start drawing a month later
+ .format('YYYY-MM-DD')
+ : null),
+
+ desired_retirement_income_monthly:
+ parseScenarioOverride(
+ localScenario.desired_retirement_income_monthly,
+ scenarioOverrides.monthlyExpenses // ← fallback to current spend
+ ),
studentLoanAmount: collegeData.studentLoanAmount,
interestRate: collegeData.interestRate,
@@ -354,13 +358,12 @@ export default function ScenarioContainer({
simulateFinancialProjection(mergedProfile);
- const sliceTo = simYearsUI * 12; // months we want to keep
- let cumulative = mergedProfile.emergencySavings || 0;
-
- const finalData = pData.slice(0, sliceTo).map(row => {
- cumulative += row.netSavings || 0;
- return { ...row, cumulativeNetSavings: cumulative };
- });
+ const sliceTo = simYearsUI * 12;
+ let cumulative = mergedProfile.emergencySavings || 0;
+ const finalData = pData.map(row => {
+ cumulative += row.netSavings || 0;
+ return { ...row, cumulativeNetSavings: cumulative };
+ }).slice(0, sliceTo);
if (typeof onSimDone === 'function') {
onSimDone(localScenario.id, yc);
@@ -958,6 +961,23 @@ return (
+ {(!localScenario?.retirement_start_date ||
+ !localScenario?.desired_retirement_income_monthly) && (
+
+
+ Add a retirement date and spending goal to see
+ Money Lasts.
+
+
+
+ )}
+
{/* ───────────── KPI Bar ───────────── */}
{projectionData.length > 0 && (
diff --git a/user_profile.db b/user_profile.db
index 140a195..20d3853 100644
Binary files a/user_profile.db and b/user_profile.db differ