Fixed MilestoneTracker.js to be the Where Am I now section.
This commit is contained in:
parent
98661b1c5a
commit
0ea62392dd
@ -71,14 +71,17 @@ const authenticatePremiumUser = (req, res, next) => {
|
|||||||
// GET the latest selected career profile
|
// GET the latest selected career profile
|
||||||
app.get('/api/premium/career-profile/latest', authenticatePremiumUser, async (req, res) => {
|
app.get('/api/premium/career-profile/latest', authenticatePremiumUser, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const [rows] = await pool.query(`
|
const sql = `
|
||||||
SELECT *
|
SELECT
|
||||||
|
*,
|
||||||
|
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date,
|
||||||
|
DATE_FORMAT(projected_end_date, '%Y-%m-%d') AS projected_end_date
|
||||||
FROM career_profiles
|
FROM career_profiles
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
ORDER BY start_date DESC
|
ORDER BY start_date DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`, [req.id]);
|
`;
|
||||||
|
const [rows] = await pool.query(sql, [req.id]);
|
||||||
res.json(rows[0] || {});
|
res.json(rows[0] || {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching latest career profile:', error);
|
console.error('Error fetching latest career profile:', error);
|
||||||
@ -89,13 +92,16 @@ app.get('/api/premium/career-profile/latest', authenticatePremiumUser, async (re
|
|||||||
// GET all career profiles for the user
|
// GET all career profiles for the user
|
||||||
app.get('/api/premium/career-profile/all', authenticatePremiumUser, async (req, res) => {
|
app.get('/api/premium/career-profile/all', authenticatePremiumUser, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const [rows] = await pool.query(`
|
const sql = `
|
||||||
SELECT *
|
SELECT
|
||||||
|
*,
|
||||||
|
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date,
|
||||||
|
DATE_FORMAT(projected_end_date, '%Y-%m-%d') AS projected_end_date
|
||||||
FROM career_profiles
|
FROM career_profiles
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
ORDER BY start_date ASC
|
ORDER BY start_date ASC
|
||||||
`, [req.id]);
|
`;
|
||||||
|
const [rows] = await pool.query(sql, [req.id]);
|
||||||
res.json({ careerProfiles: rows });
|
res.json({ careerProfiles: rows });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching career profiles:', error);
|
console.error('Error fetching career profiles:', error);
|
||||||
@ -107,13 +113,18 @@ app.get('/api/premium/career-profile/all', authenticatePremiumUser, async (req,
|
|||||||
app.get('/api/premium/career-profile/:careerProfileId', authenticatePremiumUser, async (req, res) => {
|
app.get('/api/premium/career-profile/:careerProfileId', authenticatePremiumUser, async (req, res) => {
|
||||||
const { careerProfileId } = req.params;
|
const { careerProfileId } = req.params;
|
||||||
try {
|
try {
|
||||||
const [rows] = await pool.query(`
|
const sql = `
|
||||||
SELECT *
|
SELECT
|
||||||
|
*,
|
||||||
|
DATE_FORMAT(start_date, '%Y-%m-%d') AS start_date,
|
||||||
|
DATE_FORMAT(projected_end_date, '%Y-%m-%d') AS projected_end_date
|
||||||
FROM career_profiles
|
FROM career_profiles
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
AND user_id = ?
|
AND user_id = ?
|
||||||
`, [careerProfileId, req.id]);
|
LIMIT 1
|
||||||
|
`;
|
||||||
|
const [rows] = await pool.query(sql, [careerProfileId, req.id]);
|
||||||
|
|
||||||
if (!rows[0]) {
|
if (!rows[0]) {
|
||||||
return res.status(404).json({ error: 'Career profile not found or not yours.' });
|
return res.status(404).json({ error: 'Career profile not found or not yours.' });
|
||||||
}
|
}
|
||||||
@ -124,6 +135,7 @@ app.get('/api/premium/career-profile/:careerProfileId', authenticatePremiumUser,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// POST a new career profile (upsert)
|
// POST a new career profile (upsert)
|
||||||
app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res) => {
|
app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res) => {
|
||||||
const {
|
const {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,16 @@ const OnboardingContainer = () => {
|
|||||||
const nextStep = () => setStep(step + 1);
|
const nextStep = () => setStep(step + 1);
|
||||||
const prevStep = () => setStep(step - 1);
|
const prevStep = () => setStep(step - 1);
|
||||||
|
|
||||||
|
function parseFloatOrNull(value) {
|
||||||
|
// If user left it blank ("" or undefined), treat it as NULL.
|
||||||
|
if (value == null || value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const parsed = parseFloat(value);
|
||||||
|
// If parseFloat can't parse, also return null
|
||||||
|
return isNaN(parsed) ? null : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Final collegeData in OnboardingContainer:', collegeData);
|
console.log('Final collegeData in OnboardingContainer:', collegeData);
|
||||||
|
|
||||||
// Final “all done” submission when user finishes the last step
|
// Final “all done” submission when user finishes the last step
|
||||||
@ -29,17 +39,14 @@ const OnboardingContainer = () => {
|
|||||||
// Build a scenarioPayload that includes optional planned_* fields:
|
// Build a scenarioPayload that includes optional planned_* fields:
|
||||||
const scenarioPayload = {
|
const scenarioPayload = {
|
||||||
...careerData,
|
...careerData,
|
||||||
planned_monthly_expenses: parseFloat(careerData.planned_monthly_expenses) || 0,
|
planned_monthly_expenses: parseFloatOrNull(careerData.planned_monthly_expenses),
|
||||||
planned_monthly_debt_payments: parseFloat(careerData.planned_monthly_debt_payments) || 0,
|
planned_monthly_debt_payments: parseFloatOrNull(careerData.planned_monthly_debt_payments),
|
||||||
planned_monthly_retirement_contribution:
|
planned_monthly_retirement_contribution: parseFloatOrNull(careerData.planned_monthly_retirement_contribution),
|
||||||
parseFloat(careerData.planned_monthly_retirement_contribution) || 0,
|
planned_monthly_emergency_contribution: parseFloatOrNull(careerData.planned_monthly_emergency_contribution),
|
||||||
planned_monthly_emergency_contribution:
|
planned_surplus_emergency_pct: parseFloatOrNull(careerData.planned_surplus_emergency_pct),
|
||||||
parseFloat(careerData.planned_monthly_emergency_contribution) || 0,
|
planned_surplus_retirement_pct: parseFloatOrNull(careerData.planned_surplus_retirement_pct),
|
||||||
planned_surplus_emergency_pct: parseFloat(careerData.planned_surplus_emergency_pct) || 0,
|
planned_additional_income: parseFloatOrNull(careerData.planned_additional_income),
|
||||||
planned_surplus_retirement_pct:
|
};
|
||||||
parseFloat(careerData.planned_surplus_retirement_pct) || 0,
|
|
||||||
planned_additional_income: parseFloat(careerData.planned_additional_income) || 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1) POST career-profile (scenario)
|
// 1) POST career-profile (scenario)
|
||||||
const careerRes = await authFetch('/api/premium/career-profile', {
|
const careerRes = await authFetch('/api/premium/career-profile', {
|
||||||
@ -62,21 +69,28 @@ const OnboardingContainer = () => {
|
|||||||
});
|
});
|
||||||
if (!financialRes.ok) throw new Error('Failed to save financial profile');
|
if (!financialRes.ok) throw new Error('Failed to save financial profile');
|
||||||
|
|
||||||
// 3) POST college-profile (now uses career_profile_id)
|
// 3) Only do college-profile if user is "currently_enrolled" or "prospective_student"
|
||||||
const mergedCollege = {
|
if (
|
||||||
...collegeData,
|
careerData.college_enrollment_status === 'currently_enrolled' ||
|
||||||
career_profile_id,
|
careerData.college_enrollment_status === 'prospective_student'
|
||||||
college_enrollment_status: careerData.college_enrollment_status,
|
) {
|
||||||
};
|
const mergedCollege = {
|
||||||
const collegeRes = await authFetch('/api/premium/college-profile', {
|
...collegeData,
|
||||||
method: 'POST',
|
career_profile_id,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
college_enrollment_status: careerData.college_enrollment_status,
|
||||||
body: JSON.stringify(mergedCollege),
|
};
|
||||||
});
|
const collegeRes = await authFetch('/api/premium/college-profile', {
|
||||||
if (!collegeRes.ok) throw new Error('Failed to save college profile');
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(mergedCollege),
|
||||||
|
});
|
||||||
|
if (!collegeRes.ok) throw new Error('Failed to save college profile');
|
||||||
|
} else {
|
||||||
|
console.log('Skipping college-profile upsert because user is not enrolled/planning.');
|
||||||
|
}
|
||||||
|
|
||||||
// All done → navigate away
|
// Done => navigate
|
||||||
navigate('/milestone-tracker');
|
navigate('/milestone-tracker');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
// (optionally show error to user)
|
// (optionally show error to user)
|
||||||
|
@ -606,16 +606,24 @@ export default function ScenarioEditModal({
|
|||||||
collegePayload.loan_deferral_until_graduation = 1;
|
collegePayload.loan_deferral_until_graduation = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Upsert college
|
// 3) Upsert or skip
|
||||||
const colRes = await authFetch('/api/premium/college-profile', {
|
if (finalCollegeStatus === 'currently_enrolled' ||
|
||||||
method: 'POST',
|
finalCollegeStatus === 'prospective_student')
|
||||||
headers: { 'Content-Type': 'application/json' },
|
{
|
||||||
body: JSON.stringify(collegePayload)
|
const colRes = await authFetch('/api/premium/college-profile', {
|
||||||
});
|
method: 'POST',
|
||||||
if (!colRes.ok) {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
const msg2 = await colRes.text();
|
body: JSON.stringify(collegePayload),
|
||||||
throw new Error(`College upsert failed: ${msg2}`);
|
});
|
||||||
}
|
if (!colRes.ok) {
|
||||||
|
const msg2 = await colRes.text();
|
||||||
|
throw new Error(`College upsert failed: ${msg2}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Skipping college-profile upsert in EditScenarioModal because user not enrolled');
|
||||||
|
// Optionally: if you want to delete an existing college profile:
|
||||||
|
// await authFetch(`/api/premium/college-profile/delete/${updatedScenarioId}`, { method: 'DELETE' });
|
||||||
|
}
|
||||||
|
|
||||||
// 4) Re-fetch scenario, college, financial => aggregator => simulate
|
// 4) Re-fetch scenario, college, financial => aggregator => simulate
|
||||||
const [scenResp2, colResp2, finResp] = await Promise.all([
|
const [scenResp2, colResp2, finResp] = await Promise.all([
|
||||||
|
@ -433,6 +433,7 @@ export function simulateFinancialProjection(userProfile) {
|
|||||||
loanBalance: +loanBalance.toFixed(2),
|
loanBalance: +loanBalance.toFixed(2),
|
||||||
|
|
||||||
loanPaymentThisMonth: +(monthlyLoanPayment + extraPayment).toFixed(2),
|
loanPaymentThisMonth: +(monthlyLoanPayment + extraPayment).toFixed(2),
|
||||||
|
totalSavings: (currentEmergencySavings + currentRetirementSavings).toFixed(2),
|
||||||
|
|
||||||
fedYTDgross: +fedYTDgross.toFixed(2),
|
fedYTDgross: +fedYTDgross.toFixed(2),
|
||||||
fedYTDtax: +fedYTDtax.toFixed(2),
|
fedYTDtax: +fedYTDtax.toFixed(2),
|
||||||
|
22
src/utils/fetchCareerEnrichment.js
Normal file
22
src/utils/fetchCareerEnrichment.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// utils/fetchCareerEnrichment.js
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export async function fetchCareerEnrichment(apiUrl, socCode, area) {
|
||||||
|
// strippedSoc = remove decimals from e.g. "15-1132.00" => "15-1132"
|
||||||
|
const strippedSoc = socCode.includes('.') ? socCode.split('.')[0] : socCode;
|
||||||
|
|
||||||
|
const [cipData, jobDetailsData, economicData, salaryData] = await Promise.all([
|
||||||
|
axios.get(`${apiUrl}/cip/${socCode}`).catch(() => null),
|
||||||
|
axios.get(`${apiUrl}/onet/career-description/${socCode}`).catch(() => null),
|
||||||
|
axios.get(`${apiUrl}/projections/${strippedSoc}`, { params: { area } }).catch(() => null),
|
||||||
|
axios.get(`${apiUrl}/salary`, { params: { socCode: strippedSoc, area } }).catch(() => null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cip: cipData?.data || null,
|
||||||
|
jobDetails: jobDetailsData?.data || null,
|
||||||
|
economic: economicData?.data || null,
|
||||||
|
salary: salaryData?.data || null,
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user