diff --git a/backend/server3.js b/backend/server3.js
index 88eaf44..34d2a4b 100644
--- a/backend/server3.js
+++ b/backend/server3.js
@@ -10,6 +10,9 @@ import { v4 as uuidv4 } from 'uuid';
import path from 'path';
import { fileURLToPath } from 'url';
+
+
+
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -374,11 +377,11 @@ app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req,
const {
currentSalary, additionalIncome, monthlyExpenses, monthlyDebtPayments,
retirementSavings, retirementContribution, emergencyFund,
- inCollege, expectedGraduation, partTimeIncome, tuitionPaid, collegeLoanTotal
+ inCollege, expectedGraduation, partTimeIncome, tuitionPaid, collegeLoanTotal,
+ careerPathId // ✅ Required to run simulation
} = req.body;
try {
- // Upsert-style logic: Check if exists
const existing = await db.get(`SELECT id FROM financial_profile WHERE user_id = ?`, [req.userId]);
if (existing) {
@@ -409,7 +412,36 @@ app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req,
]);
}
- res.status(200).json({ message: 'Financial profile saved.' });
+ // ✅ Run projection only if careerPathId is provided
+ if (!careerPathId) {
+ return res.status(200).json({ message: 'Financial profile saved. No projection generated.' });
+ }
+
+ const milestones = await db.all(
+ `SELECT * FROM milestones WHERE user_id = ? AND career_path_id = ? ORDER BY date ASC`,
+ [req.userId, careerPathId]
+ );
+
+ const projectionData = simulateFinancialProjection({
+ currentSalary,
+ additionalIncome,
+ monthlyExpenses,
+ monthlyDebtPayments,
+ retirementSavings,
+ retirementContribution,
+ emergencySavings: emergencyFund,
+ studentLoanAmount: collegeLoanTotal,
+ studentLoanAPR: 5.5, // placeholder default, can be user-supplied later
+ loanTermYears: 10, // placeholder default, can be user-supplied later
+ milestones,
+ gradDate: expectedGraduation,
+ fullTimeCollegeStudent: !!inCollege,
+ partTimeIncome,
+ startDate: moment()
+ });
+
+ return res.status(200).json({ message: 'Financial profile saved.', projectionData });
+
} catch (error) {
console.error('Error saving financial profile:', error);
res.status(500).json({ error: 'Failed to save financial profile.' });
@@ -417,6 +449,7 @@ app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req,
});
+
// Retrieve career history
app.get('/api/premium/career-history', authenticatePremiumUser, async (req, res) => {
try {
diff --git a/package-lock.json b/package-lock.json
index 4aa728c..3f9f41f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"chart.js": "^4.4.7",
+ "chartjs-plugin-annotation": "^3.1.0",
"class-variance-authority": "^0.7.1",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
@@ -26,6 +27,7 @@
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.483.0",
+ "moment": "^2.30.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
@@ -6191,6 +6193,15 @@
"pnpm": ">=8"
}
},
+ "node_modules/chartjs-plugin-annotation": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.1.0.tgz",
+ "integrity": "sha512-EkAed6/ycXD/7n0ShrlT1T2Hm3acnbFhgkIEJLa0X+M6S16x0zwj1Fv4suv/2bwayCT3jGPdAtI9uLcAMToaQQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": ">=4.0.0"
+ }
+ },
"node_modules/check-types": {
"version": "11.2.3",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz",
@@ -12830,6 +12841,15 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -18429,9 +18449,9 @@
}
},
"node_modules/typescript": {
- "version": "5.8.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
- "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"license": "Apache-2.0",
"peer": true,
"bin": {
@@ -18439,7 +18459,7 @@
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=14.17"
+ "node": ">=4.2.0"
}
},
"node_modules/unbox-primitive": {
diff --git a/package.json b/package.json
index 24e1a82..cd93fc7 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"chart.js": "^4.4.7",
+ "chartjs-plugin-annotation": "^3.1.0",
"class-variance-authority": "^0.7.1",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
@@ -21,6 +22,7 @@
"jsonwebtoken": "^9.0.2",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.483.0",
+ "moment": "^2.30.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
diff --git a/src/App.js b/src/App.js
index f9cbd8a..ee6e42b 100644
--- a/src/App.js
+++ b/src/App.js
@@ -12,7 +12,8 @@ import MilestoneTracker from "./components/MilestoneTracker.js";
import './App.css';
function App() {
-
+ console.log("App rendered");
+
const [isAuthenticated, setIsAuthenticated] = useState(() => {
return !!localStorage.getItem('token'); // Check localStorage
});
@@ -29,37 +30,22 @@ function App() {
} />
{/* Protected routes */}
- : }
- />
- : }
- />
- : }
- />
- : }
- />
- : }
- />
- : }
- />
+ {isAuthenticated && (
+ <>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ >
+ )}
{/* Catch-all for unknown routes */}
} />
-
);
}
diff --git a/src/components/AISuggestedMilestones.js b/src/components/AISuggestedMilestones.js
index c13b608..3284e8f 100644
--- a/src/components/AISuggestedMilestones.js
+++ b/src/components/AISuggestedMilestones.js
@@ -2,11 +2,21 @@
import React, { useEffect, useState } from 'react';
-const AISuggestedMilestones = ({ userId, career, careerPathId, authFetch, activeView }) => {
+const AISuggestedMilestones = ({ userId, career, careerPathId, authFetch, activeView, projectionData }) => {
const [suggestedMilestones, setSuggestedMilestones] = useState([]);
const [selected, setSelected] = useState([]);
const [loading, setLoading] = useState(false);
+ useEffect(() => {
+ if (!Array.isArray(projectionData)) {
+ console.warn('⚠️ projectionData is not an array:', projectionData);
+ return;
+ }
+
+ console.log('📊 projectionData sample:', projectionData.slice(0, 3));
+ }, [projectionData]);
+
+
useEffect(() => {
if (!career) return;
setSuggestedMilestones([
diff --git a/src/components/FinancialProfileForm.js b/src/components/FinancialProfileForm.js
index b0fbd64..ec1967b 100644
--- a/src/components/FinancialProfileForm.js
+++ b/src/components/FinancialProfileForm.js
@@ -1,75 +1,199 @@
+// Updated FinancialProfileForm.js with autosuggest for school and full field list restored
import React, { useState, useEffect } from "react";
import { useLocation, useNavigate } from 'react-router-dom';
import authFetch from '../utils/authFetch.js';
+function FinancialProfileForm() {
+ const navigate = useNavigate();
+ const location = useLocation();
-export default function FinancialProfileForm() {
- const location = useLocation();
- const navigate = useNavigate();
- console.log("🔍 FinancialProfileForm mounted");
- console.log("🔍 location.state:", location.state);
- const initialCareer = location?.state?.selectedCareer;
- const [selectedCareer, setSelectedCareer] = useState(initialCareer || null);
- const userId = localStorage.getItem("userId");
- const [formData, setFormData] = useState({
- currentSalary: "",
- additionalIncome: "",
- monthlyExpenses: "",
- monthlyDebtPayments: "",
- retirementSavings: "",
- retirementContribution: "",
- emergencyFund: "",
- inCollege: false,
- expectedGraduation: "",
- partTimeIncome: "",
- tuitionPaid: "",
- collegeLoanTotal: ""
- });
+ const [userId] = useState(() => localStorage.getItem("userId"));
+ const [selectedCareer] = useState(() => location.state?.selectedCareer || null);
+
+ const [currentSalary, setCurrentSalary] = useState("");
+ const [additionalIncome, setAdditionalIncome] = useState("");
+ const [monthlyExpenses, setMonthlyExpenses] = useState("");
+ const [monthlyDebtPayments, setMonthlyDebtPayments] = useState("");
+ const [retirementSavings, setRetirementSavings] = useState("");
+ const [retirementContribution, setRetirementContribution] = useState("");
+ const [emergencyFund, setEmergencyFund] = useState("");
+ const [inCollege, setInCollege] = useState(false);
+ const [expectedGraduation, setExpectedGraduation] = useState("");
+ const [partTimeIncome, setPartTimeIncome] = useState("");
+ const [tuitionPaid, setTuitionPaid] = useState("");
+ const [collegeLoanTotal, setCollegeLoanTotal] = useState("");
+ const [existingCollegeDebt, setExistingCollegeDebt] = useState("");
+ const [creditHoursPerYear, setCreditHoursPerYear] = useState("");
+ const [programType, setProgramType] = useState("");
+ const [isFullyOnline, setIsFullyOnline] = useState(false);
+ const [selectedSchool, setSelectedSchool] = useState("");
+ const [selectedProgram, setSelectedProgram] = useState("");
+ const [manualTuition, setManualTuition] = useState("");
+
+ const [schoolData, setSchoolData] = useState([]);
+ const [schoolSuggestions, setSchoolSuggestions] = useState([]);
+ const [programSuggestions, setProgramSuggestions] = useState([]);
+ const [availableProgramTypes, setAvailableProgramTypes] = useState([]);
+ const [calculatedTuition, setCalculatedTuition] = useState(0);
useEffect(() => {
- console.log("✅ selectedCareer in useEffect:", selectedCareer);
- }, [selectedCareer]);
-
- // Fetch existing data on mount
- useEffect(() => {
- async function fetchFinancialProfile() {
- try {
- const res = await authFetch("/api/premium/financial-profile", {
- method: "GET",
- headers: {
- "Authorization": `Bearer ${localStorage.getItem('token')}`
- }
- });
-
- if (res.ok) {
- const data = await res.json();
- if (data && Object.keys(data).length > 0) {
- setFormData((prev) => ({ ...prev, ...data }));
- } else {
- console.log("No existing financial profile. Starting fresh.");
+ async function fetchSchoolData() {
+ const res = await fetch('/cip_institution_mapping_new.json');
+ const text = await res.text();
+ const lines = text.split('\n');
+ const parsed = lines.map(line => {
+ try {
+ return JSON.parse(line);
+ } catch {
+ return null;
}
- } else {
- console.warn("Response not OK when fetching financial profile:", res.status);
- }
- } catch (err) {
- console.error("Failed to fetch financial profile", err);
+ }).filter(Boolean);
+ setSchoolData(parsed);
}
- }
+ fetchSchoolData();
+ }, []);
- fetchFinancialProfile();
-}, [userId]);
+ useEffect(() => {
+ async function fetchFinancialProfile() {
+ try {
+ const res = await authFetch("/api/premium/financial-profile", {
+ method: "GET",
+ headers: { "Authorization": `Bearer ${localStorage.getItem('token')}` }
+ });
+ if (res.ok) {
+ const data = await res.json();
+ if (data && Object.keys(data).length > 0) {
+ setCurrentSalary(data.current_salary || "");
+ setAdditionalIncome(data.additional_income || "");
+ setMonthlyExpenses(data.monthly_expenses || "");
+ setMonthlyDebtPayments(data.monthly_debt_payments || "");
+ setRetirementSavings(data.retirement_savings || "");
+ setRetirementContribution(data.retirement_contribution || "");
+ setEmergencyFund(data.emergency_fund || "");
+ setInCollege(!!data.in_college);
+ setExpectedGraduation(data.expected_graduation || "");
+ setPartTimeIncome(data.part_time_income || "");
+ setTuitionPaid(data.tuition_paid || "");
+ setCollegeLoanTotal(data.college_loan_total || "");
+ setExistingCollegeDebt(data.existing_college_debt || "");
+ setCreditHoursPerYear(data.credit_hours_per_year || "");
+ setProgramType(data.program_type || "");
+ setIsFullyOnline(!!data.is_fully_online);
+ setSelectedSchool(data.selected_school || "");
+ setSelectedProgram(data.selected_program || "");
+ }
+ }
+ } catch (err) {
+ console.error("Failed to fetch financial profile", err);
+ }
+ }
- const handleChange = (e) => {
- const { name, value, type, checked } = e.target;
- setFormData((prev) => ({
- ...prev,
- [name]: type === "checkbox" ? checked : value
- }));
+ fetchFinancialProfile();
+ }, [userId]);
+
+ useEffect(() => {
+ if (selectedSchool && schoolData.length > 0) {
+ // Filter programs for the selected school and display them as suggestions
+ const programs = schoolData
+ .filter(s => s.INSTNM.toLowerCase() === selectedSchool.toLowerCase())
+ .map(s => s.CIPDESC);
+
+ // Filter unique programs and show the first 10
+ setProgramSuggestions([...new Set(programs)].slice(0, 10));
+ }
+ }, [selectedSchool, schoolData]);
+
+ useEffect(() => {
+ if (selectedProgram && selectedSchool && schoolData.length > 0) {
+ const types = schoolData
+ .filter(s => s.CIPDESC === selectedProgram && s.INSTNM.toLowerCase() === selectedSchool.toLowerCase())
+ .map(s => s.CREDDESC);
+ setAvailableProgramTypes([...new Set(types)]);
+ }
+ }, [selectedProgram, selectedSchool, schoolData]);
+
+ useEffect(() => {
+ if (selectedSchool && selectedProgram && programType && schoolData.length > 0) {
+ const match = schoolData.find(s =>
+ s.INSTNM.toLowerCase() === selectedSchool.toLowerCase() &&
+ s.CIPDESC === selectedProgram &&
+ s.CREDDESC === programType
+ );
+ const tuition = match ? parseFloat(match[isFullyOnline ? "Out State Graduate" : "In_state cost"] || 0) : 0;
+ setCalculatedTuition(tuition);
+ }
+ }, [selectedSchool, selectedProgram, programType, isFullyOnline, schoolData]);
+
+ const handleSchoolChange = (e) => {
+ const value = e.target.value;
+ setSelectedSchool(value);
+ const filtered = schoolData.filter(s => s.INSTNM.toLowerCase().includes(value.toLowerCase()));
+ const unique = [...new Set(filtered.map(s => s.INSTNM))];
+ setSchoolSuggestions(unique.slice(0, 10));
+ setSelectedProgram("");
+ setAvailableProgramTypes([]);
+ };
+
+ const handleSchoolSelect = (name) => {
+ setSelectedSchool(name);
+ setSchoolSuggestions([]);
+ setSelectedProgram("");
+ setAvailableProgramTypes([]);
+ setProgramSuggestions([]);
+ };
+
+ const handleProgramChange = (e) => {
+ const value = e.target.value;
+ setSelectedProgram(value);
+
+ if (!value) {
+ setProgramSuggestions([]);
+ return;
+ }
+
+ const filtered = schoolData.filter(s =>
+ s.INSTNM.toLowerCase() === selectedSchool.toLowerCase() &&
+ s.CIPDESC.toLowerCase().includes(value.toLowerCase())
+ );
+
+ const uniquePrograms = [...new Set(filtered.map(s => s.CIPDESC))];
+ setProgramSuggestions(uniquePrograms);
+
+ const filteredTypes = schoolData.filter(s =>
+ s.INSTNM.toLowerCase() === selectedSchool.toLowerCase() &&
+ s.CIPDESC === value
+ ).map(s => s.CREDDESC);
+ setAvailableProgramTypes([...new Set(filteredTypes)]);
+ };
+
+ const handleProgramTypeSelect = (e) => {
+ setProgramType(e.target.value);
};
const handleSubmit = async (e) => {
e.preventDefault();
+ const formData = {
+ currentSalary,
+ additionalIncome,
+ monthlyExpenses,
+ monthlyDebtPayments,
+ retirementSavings,
+ retirementContribution,
+ emergencyFund,
+ inCollege,
+ expectedGraduation,
+ partTimeIncome,
+ tuitionPaid,
+ collegeLoanTotal,
+ existingCollegeDebt,
+ creditHoursPerYear,
+ programType,
+ isFullyOnline,
+ selectedSchool,
+ selectedProgram
+ };
+
try {
const res = await authFetch("/api/premium/financial-profile", {
method: "POST",
@@ -81,90 +205,126 @@ export default function FinancialProfileForm() {
navigate('/milestone-tracker', {
state: { selectedCareer }
});
- }
+ }
} catch (err) {
console.error("Error submitting financial profile:", err);
}
};
+ const handleInput = (setter) => (e) => setter(e.target.value);
+
return (