Fixed jwt token/user_auth.user_id vs. user_profile.id. College/financial/Premium Onboarding calls, ReviewPage info.

This commit is contained in:
Josh 2025-06-03 11:39:04 +00:00
parent 0c8cd4a969
commit 287737fa8b
6 changed files with 341 additions and 207 deletions

View File

@ -180,8 +180,8 @@ app.post('/api/register', async (req, res) => {
// 2) Insert into user_auth, referencing user_profile.id // 2) Insert into user_auth, referencing user_profile.id
const authQuery = ` const authQuery = `
INSERT INTO user_auth (id, username, hashed_password) INSERT INTO user_auth (user_id, username, hashed_password)
VALUES (?, ?, ?) VALUES (?, ?, ?)
`; `;
pool.query( pool.query(
authQuery, authQuery,
@ -219,7 +219,7 @@ app.post('/api/register', async (req, res) => {
* Body: { username, password } * Body: { username, password }
* Returns JWT signed with user_profile.id * Returns JWT signed with user_profile.id
*/ */
app.post('/api/signin', (req, res) => { app.post('/api/signin', async (req, res) => {
const { username, password } = req.body; const { username, password } = req.body;
if (!username || !password) { if (!username || !password) {
return res return res
@ -227,25 +227,28 @@ app.post('/api/signin', (req, res) => {
.json({ error: 'Both username and password are required' }); .json({ error: 'Both username and password are required' });
} }
// SELECT only the columns you actually have:
// 'ua.id' is user_auth's primary key,
// 'ua.user_id' references user_profile.id,
// and we alias user_profile.id as profileId for clarity.
const query = ` const query = `
SELECT SELECT
user_auth.id, ua.id AS authId,
user_auth.hashed_password, ua.user_id AS userProfileId,
user_profile.firstname, ua.hashed_password,
user_profile.lastname, up.firstname,
user_profile.email, up.lastname,
user_profile.zipcode, up.email,
user_profile.state, up.zipcode,
user_profile.area, up.state,
user_profile.is_premium, up.area,
user_profile.is_pro_premium, up.career_situation
user_profile.career_situation, FROM user_auth ua
user_profile.career_priorities, LEFT JOIN user_profile up
user_profile.career_list ON ua.user_id = up.id
FROM user_auth WHERE ua.username = ?
LEFT JOIN user_profile ON user_auth.id = user_profile.id
WHERE user_auth.username = ?
`; `;
pool.query(query, [username], async (err, results) => { pool.query(query, [username], async (err, results) => {
if (err) { if (err) {
console.error('Error querying user_auth:', err.message); console.error('Error querying user_auth:', err.message);
@ -259,22 +262,26 @@ app.post('/api/signin', (req, res) => {
} }
const row = results[0]; const row = results[0];
// Compare password
// Compare password with bcrypt
const isMatch = await bcrypt.compare(password, row.hashed_password); const isMatch = await bcrypt.compare(password, row.hashed_password);
if (!isMatch) { if (!isMatch) {
return res.status(401).json({ error: 'Invalid username or password' }); return res.status(401).json({ error: 'Invalid username or password' });
} }
// The user_profile id is stored in user_auth.id // IMPORTANT: Use 'row.userProfileId' (from user_profile.id) in the token
const token = jwt.sign({ id: row.id }, SECRET_KEY, { // so your '/api/user-profile' can decode it and do SELECT * FROM user_profile WHERE id=?
const token = jwt.sign({ id: row.userProfileId }, SECRET_KEY, {
expiresIn: '2h', expiresIn: '2h',
}); });
// Return the user info + token // Return user info + token
// 'authId' is user_auth's PK, but typically you won't need it on the client
// 'row.userProfileId' is the actual user_profile.id
res.status(200).json({ res.status(200).json({
message: 'Login successful', message: 'Login successful',
token, token,
id: row.id, // The user_profile.id id: row.userProfileId, // This is user_profile.id (important if your frontend needs it)
user: { user: {
firstname: row.firstname, firstname: row.firstname,
lastname: row.lastname, lastname: row.lastname,
@ -282,16 +289,14 @@ app.post('/api/signin', (req, res) => {
zipcode: row.zipcode, zipcode: row.zipcode,
state: row.state, state: row.state,
area: row.area, area: row.area,
is_premium: row.is_premium,
is_pro_premium: row.is_pro_premium,
career_situation: row.career_situation, career_situation: row.career_situation,
career_priorities: row.career_priorities,
career_list: row.career_list,
}, },
}); });
}); });
}); });
/* ------------------------------------------------------------------ /* ------------------------------------------------------------------
CHECK USERNAME (MySQL) CHECK USERNAME (MySQL)
------------------------------------------------------------------ */ ------------------------------------------------------------------ */

View File

@ -0,0 +1,129 @@
import React, { useState } from 'react';
import { Button } from './ui/button.js';
function ExpensesWizard({ onClose, onExpensesCalculated }) {
const [housing, setHousing] = useState('');
const [utilities, setUtilities] = useState('');
const [groceries, setGroceries] = useState('');
const [transportation, setTransportation] = useState('');
const [insurance, setInsurance] = useState('');
const [misc, setMisc] = useState('');
const calculateTotal = () => {
const sum =
(parseFloat(housing) || 0) +
(parseFloat(utilities) || 0) +
(parseFloat(groceries) || 0) +
(parseFloat(transportation) || 0) +
(parseFloat(insurance) || 0) +
(parseFloat(misc) || 0);
return sum;
};
const handleFinish = () => {
const total = calculateTotal();
onExpensesCalculated(total);
onClose();
};
const totalExpenses = calculateTotal();
return (
<div className="p-4 bg-white rounded shadow-md">
<h2 className="text-xl font-semibold mb-4">Monthly Expenses Wizard</h2>
<p className="text-sm mb-4">
Enter approximate amounts for each category below. We'll sum them up to estimate
your monthly expenses.
</p>
<div className="mb-2">
<label className="block text-sm font-medium">
Housing (Rent/Mortgage)
</label>
<input
type="number"
className="border p-2 rounded w-full"
value={housing}
onChange={(e) => setHousing(e.target.value)}
placeholder="e.g. 1500"
/>
</div>
<div className="mb-2">
<label className="block text-sm font-medium">Utilities</label>
<input
type="number"
className="border p-2 rounded w-full"
value={utilities}
onChange={(e) => setUtilities(e.target.value)}
placeholder="Water, electricity, gas, etc. (e.g. 200)"
/>
</div>
<div className="mb-2">
<label className="block text-sm font-medium">Food</label>
<input
type="number"
className="border p-2 rounded w-full"
value={groceries}
onChange={(e) => setGroceries(e.target.value)}
placeholder="Groceries, dining out, etc. (e.g. 300)"
/>
</div>
<div className="mb-2">
<label className="block text-sm font-medium">Transportation</label>
<input
type="number"
className="border p-2 rounded w-full"
value={transportation}
onChange={(e) => setTransportation(e.target.value)}
placeholder="Car payment, gas, train, bus, uber fare e.g. 500"
/>
</div>
<div className="mb-2">
<label className="block text-sm font-medium">Insurance</label>
<input
type="number"
className="border p-2 rounded w-full"
value={insurance}
onChange={(e) => setInsurance(e.target.value)}
placeholder="Car, house, rental, health insurance not deducted from paycheck (e.g. 200)"
/>
</div>
<div className="mb-2">
<label className="block text-sm font-medium">Miscellaneous</label>
<input
type="number"
className="border p-2 rounded w-full"
value={misc}
onChange={(e) => setMisc(e.target.value)}
placeholder="Subscriptions, Phone, any recurring cost not covered elsewhere e.g. 250"
/>
</div>
{/* Show the user the current total */}
<p className="text-sm mb-4">
<strong>Current Total:</strong> ${totalExpenses.toFixed(2)}
</p>
<div className="mt-4 flex justify-between">
<Button
className="bg-gray-200 text-gray-800 hover:bg-gray-300"
onClick={onClose}
>
Cancel
</Button>
<Button className="bg-blue-600 hover:bg-blue-700" onClick={handleFinish}>
Use This Total
</Button>
</div>
</div>
);
}
export default ExpensesWizard;

View File

@ -475,7 +475,7 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerProfileId
<button <button
type="button" type="button"
onClick={() => setShowAidWizard(true)} onClick={() => setShowAidWizard(true)}
className="bg-gray-200 px-3 py-2 rounded" className="bg-blue-600 text-center px-3 py-2 rounded"
> >
Need Help? Need Help?
</button> </button>

View File

@ -1,7 +1,10 @@
// FinancialOnboarding.js // FinancialOnboarding.js
import React from 'react'; import React, { useState } from 'react';
import Modal from '../ui/modal.js';
import ExpensesWizard from '../../components/ExpensesWizard.js'; // path to your wizard
import { Button } from '../../components/ui/button.js'; // using your Tailwind-based button
const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = false }) => { const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => {
const { const {
currently_working = '', currently_working = '',
current_salary = 0, current_salary = 0,
@ -14,19 +17,37 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
emergency_contribution = 0, emergency_contribution = 0,
extra_cash_emergency_pct = "", extra_cash_emergency_pct = "",
extra_cash_retirement_pct = "", extra_cash_retirement_pct = "",
planned_monthly_expenses = '',
planned_monthly_debt_payments = '',
planned_monthly_retirement_contribution = '',
planned_monthly_emergency_contribution = '',
planned_surplus_emergency_pct = '',
planned_surplus_retirement_pct = '',
planned_additional_income = ''
} = data; } = data;
const handleChange = (e) => { const [showExpensesWizard, setShowExpensesWizard] = useState(false);
const { name, value } = e.target;
let val = parseFloat(value) || 0;
const handleNeedHelpExpenses = () => {
setShowExpensesWizard(true);
};
const handleExpensesCalculated = (total) => {
setData(prev => ({
...prev,
monthly_expenses: total
}));
};
const infoIcon = (msg) => (
<span
className="ml-1 inline-flex h-4 w-4 items-center justify-center rounded-full bg-blue-500 text-white text-xs cursor-help"
title={msg}
>
i
</span>
);
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
let val = parseFloat(value) || 0;
if (type === 'checkbox') {
val = checked;
}
if (name === 'extra_cash_emergency_pct') { if (name === 'extra_cash_emergency_pct') {
val = Math.min(Math.max(val, 0), 100); val = Math.min(Math.max(val, 0), 100);
setData(prevData => ({ setData(prevData => ({
@ -46,6 +67,11 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
} }
}; };
const handleSubmit = () => {
// Move to next step
nextStep();
};
return ( return (
<div className="max-w-md mx-auto p-6 space-y-6"> <div className="max-w-md mx-auto p-6 space-y-6">
<h2 className="text-2xl font-semibold">Financial Details</h2> <h2 className="text-2xl font-semibold">Financial Details</h2>
@ -53,11 +79,13 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
{currently_working === 'yes' && ( {currently_working === 'yes' && (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block font-medium">Current Annual Salary</label> <label className="block font-medium">Current Annual Salary
{infoIcon("Gross annual salary before taxes")}
</label>
<input <input
name="current_salary" name="current_salary"
type="number" type="number"
placeholder="Current Annual Salary" placeholder="e.g. 110000"
value={current_salary || ''} value={current_salary || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -65,11 +93,13 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
</div> </div>
<div> <div>
<label className="block font-medium">Additional Annual Income</label> <label className="block font-medium">Additional Annual Income (optional)
{infoIcon("Yearly bonuses, investment returns, side jobs, etc.")}
</label>
<input <input
name="additional_income" name="additional_income"
type="number" type="number"
placeholder="Investments, side jobs, etc. (optional)" placeholder="e.g. 2400"
value={additional_income || ''} value={additional_income || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -79,24 +109,34 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
)} )}
<div className="space-y-4"> <div className="space-y-4">
<div> <label className="block font-medium"> Monthly Expenses
<label className="block font-medium">Monthly Expenses</label> {infoIcon("The total amount you spend on rent, utilities, groceries, etc.")}
</label>
<div className="flex space-x-2 items-center">
<input <input
name="monthly_expenses"
type="number" type="number"
placeholder="Monthly Expenses"
value={monthly_expenses || ''}
onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
name="monthly_expenses"
value={monthly_expenses}
onChange={handleChange}
placeholder="e.g. 1500"
/> />
<Button
className="bg-blue-600 text-center px-3 py-2 rounded"
onClick={handleNeedHelpExpenses}
>
Need Help?
</Button>
</div> </div>
<div> <div>
<label className="block font-medium">Monthly Debt Payments (optional)</label> <label className="block font-medium">Monthly Debt Payments (optional)
{infoIcon("If you keep installment loans on cars, credit cards, etc. separate from other living expenses")}
</label>
<input <input
name="monthly_debt_payments" name="monthly_debt_payments"
type="number" type="number"
placeholder="Monthly Debt Payments" placeholder="e.g. 500"
value={monthly_debt_payments || ''} value={monthly_debt_payments || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -104,11 +144,13 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
</div> </div>
<div> <div>
<label className="block font-medium">Retirement Savings</label> <label className="block font-medium">Retirement Savings (optional)
{infoIcon("Current Retirement Balance")}
</label>
<input <input
name="retirement_savings" name="retirement_savings"
type="number" type="number"
placeholder="Retirement Savings" placeholder="e.g. 50000"
value={retirement_savings || ''} value={retirement_savings || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -116,11 +158,13 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
</div> </div>
<div> <div>
<label className="block font-medium">Monthly Retirement Contribution</label> <label className="block font-medium">Monthly Retirement Contribution (optional)
{infoIcon("Dollar value (not percentage) of monthly retirement contribution")}
</label>
<input <input
name="retirement_contribution" name="retirement_contribution"
type="number" type="number"
placeholder="Monthly Retirement Contribution" placeholder="e.g. 300"
value={retirement_contribution || ''} value={retirement_contribution || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -128,11 +172,13 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
</div> </div>
<div> <div>
<label className="block font-medium">Emergency Fund Savings</label> <label className="block font-medium">Emergency Fund Savings (optional)
{infoIcon("Balance of your emergency fund for job loss, medical emergencies, etc.")}
</label>
<input <input
name="emergency_fund" name="emergency_fund"
type="number" type="number"
placeholder="Emergency Fund Savings" placeholder="e.g. 10000"
value={emergency_fund || ''} value={emergency_fund || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -140,11 +186,13 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
</div> </div>
<div> <div>
<label className="block font-medium">Monthly Emergency Fund Contribution</label> <label className="block font-medium">Monthly Emergency Savings Contribution (optional)
{infoIcon("Dollar value (not percentage) of monthly emergency savings contribution")}
</label>
<input <input
name="emergency_contribution" name="emergency_contribution"
type="number" type="number"
placeholder="Monthly Emergency Fund Contribution (optional)" placeholder="e.g. 300"
value={emergency_contribution || ''} value={emergency_contribution || ''}
onChange={handleChange} onChange={handleChange}
className="w-full border rounded p-2" className="w-full border rounded p-2"
@ -184,94 +232,6 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
</div> </div>
</div> </div>
{/* Only show the planned overrides if isEditMode is true */}
{isEditMode && (
<div className="space-y-4">
<hr className="my-4" />
<h2 className="text-xl font-medium">Planned Scenario Overrides</h2>
<p className="text-gray-600">
These fields let you override your real finances for this scenario.
</p>
<div>
<label className="block font-medium">Planned Monthly Expenses</label>
<input
type="number"
name="planned_monthly_expenses"
value={planned_monthly_expenses}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Monthly Debt Payments</label>
<input
type="number"
name="planned_monthly_debt_payments"
value={planned_monthly_debt_payments}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Monthly Retirement Contribution</label>
<input
type="number"
name="planned_monthly_retirement_contribution"
value={planned_monthly_retirement_contribution}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Monthly Emergency Contribution</label>
<input
type="number"
name="planned_monthly_emergency_contribution"
value={planned_monthly_emergency_contribution}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Surplus % to Emergency</label>
<input
type="number"
name="planned_surplus_emergency_pct"
value={planned_surplus_emergency_pct}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Surplus % to Retirement</label>
<input
type="number"
name="planned_surplus_retirement_pct"
value={planned_surplus_retirement_pct}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
<div>
<label className="block font-medium">Planned Additional Annual Income</label>
<input
type="number"
name="planned_additional_income"
value={planned_additional_income}
onChange={handleChange}
className="w-full border rounded p-2"
/>
</div>
</div>
)}
<div className="flex justify-between pt-4"> <div className="flex justify-between pt-4">
<button <button
onClick={prevStep} onClick={prevStep}
@ -285,6 +245,16 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData, isEditMode = f
> >
Next: College Next: College
</button> </button>
{showExpensesWizard && (
<Modal onClose={() => setShowExpensesWizard(false)}>
<ExpensesWizard
onClose={() => setShowExpensesWizard(false)}
onExpensesCalculated={handleExpensesCalculated}
/>
</Modal>
)}
</div> </div>
</div> </div>
); );

View File

@ -1,4 +1,3 @@
// OnboardingContainer.js
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import PremiumWelcome from './PremiumWelcome.js'; import PremiumWelcome from './PremiumWelcome.js';
@ -12,41 +11,62 @@ import authFetch from '../../utils/authFetch.js';
const OnboardingContainer = () => { const OnboardingContainer = () => {
console.log('OnboardingContainer MOUNT'); console.log('OnboardingContainer MOUNT');
const navigate = useNavigate();
// 1. Local state for multi-step onboarding
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const [careerData, setCareerData] = useState({}); const [careerData, setCareerData] = useState({});
const [financialData, setFinancialData] = useState({}); const [financialData, setFinancialData] = useState({});
const [collegeData, setCollegeData] = useState({}); const [collegeData, setCollegeData] = useState({});
const navigate = useNavigate();
const nextStep = () => setStep(step + 1); // 2. On mount, check if localStorage has onboarding data
const prevStep = () => setStep(step - 1); useEffect(() => {
const stored = localStorage.getItem('premiumOnboardingState');
if (stored) {
try {
const parsed = JSON.parse(stored);
// Restore step and data if they exist
if (parsed.step !== undefined) setStep(parsed.step);
if (parsed.careerData) setCareerData(parsed.careerData);
if (parsed.financialData) setFinancialData(parsed.financialData);
if (parsed.collegeData) setCollegeData(parsed.collegeData);
} catch (err) {
console.warn('Failed to parse premiumOnboardingState:', err);
}
}
}, []);
// 3. Whenever any key pieces of state change, save to localStorage
useEffect(() => {
const stateToStore = {
step,
careerData,
financialData,
collegeData
};
localStorage.setItem('premiumOnboardingState', JSON.stringify(stateToStore));
}, [step, careerData, financialData, collegeData]);
// Move user to next or previous step
const nextStep = () => setStep((prev) => prev + 1);
const prevStep = () => setStep((prev) => prev - 1);
function parseFloatOrNull(value) { function parseFloatOrNull(value) {
// If user left it blank ("" or undefined), treat it as NULL. if (value == null || value === '') {
if (value == null || value === '') { return null;
return null; }
const parsed = parseFloat(value);
return isNaN(parsed) ? null : parsed;
} }
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 // 4. Final “all done” submission
const handleFinalSubmit = async () => { const handleFinalSubmit = async () => {
try { try {
// Build a scenarioPayload that includes optional planned_* fields:
const scenarioPayload = { const scenarioPayload = {
...careerData, ...careerData,
planned_monthly_expenses: parseFloatOrNull(careerData.planned_monthly_expenses), };
planned_monthly_debt_payments: parseFloatOrNull(careerData.planned_monthly_debt_payments),
planned_monthly_retirement_contribution: parseFloatOrNull(careerData.planned_monthly_retirement_contribution),
planned_monthly_emergency_contribution: parseFloatOrNull(careerData.planned_monthly_emergency_contribution),
planned_surplus_emergency_pct: parseFloatOrNull(careerData.planned_surplus_emergency_pct),
planned_surplus_retirement_pct: parseFloatOrNull(careerData.planned_surplus_retirement_pct),
planned_additional_income: parseFloatOrNull(careerData.planned_additional_income),
};
// 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', {
@ -56,7 +76,7 @@ const OnboardingContainer = () => {
}); });
if (!careerRes.ok) throw new Error('Failed to save career profile'); if (!careerRes.ok) throw new Error('Failed to save career profile');
const careerJson = await careerRes.json(); const careerJson = await careerRes.json();
const { career_profile_id } = careerJson; // <-- Renamed from career_profile_id const { career_profile_id } = careerJson;
if (!career_profile_id) { if (!career_profile_id) {
throw new Error('No career_profile_id returned by server'); throw new Error('No career_profile_id returned by server');
} }
@ -69,34 +89,38 @@ 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) Only do college-profile if user is "currently_enrolled" or "prospective_student" // 3) Possibly POST college-profile
if ( if (
careerData.college_enrollment_status === 'currently_enrolled' || careerData.college_enrollment_status === 'currently_enrolled' ||
careerData.college_enrollment_status === 'prospective_student' careerData.college_enrollment_status === 'prospective_student'
) { ) {
const mergedCollege = { const mergedCollege = {
...collegeData, ...collegeData,
career_profile_id, career_profile_id,
college_enrollment_status: careerData.college_enrollment_status, college_enrollment_status: careerData.college_enrollment_status,
}; };
const collegeRes = await authFetch('/api/premium/college-profile', { const collegeRes = await authFetch('/api/premium/college-profile', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(mergedCollege), body: JSON.stringify(mergedCollege),
}); });
if (!collegeRes.ok) throw new Error('Failed to save college profile'); if (!collegeRes.ok) throw new Error('Failed to save college profile');
} else { } else {
console.log('Skipping college-profile upsert because user is not enrolled/planning.'); console.log('Skipping college-profile upsert because user is not enrolled/planning.');
} }
// Done => navigate // 4) Clear localStorage so next onboarding starts fresh (optional)
navigate('/milestone-tracker'); localStorage.removeItem('premiumOnboardingState');
// 5) Navigate away
navigate('/milestone-tracker');
} catch (err) { } catch (err) {
console.error(err); console.error(err);
// (optionally show error to user) // Optionally show error to user
} }
}; };
// 5. Array of steps
const onboardingSteps = [ const onboardingSteps = [
<PremiumWelcome nextStep={nextStep} />, <PremiumWelcome nextStep={nextStep} />,
@ -121,7 +145,6 @@ const OnboardingContainer = () => {
nextStep={nextStep} nextStep={nextStep}
data={{ data={{
...collegeData, ...collegeData,
// keep enrollment status from careerData if relevant:
college_enrollment_status: careerData.college_enrollment_status, college_enrollment_status: careerData.college_enrollment_status,
}} }}
setData={setCollegeData} setData={setCollegeData}

View File

@ -87,19 +87,26 @@ function ReviewPage({
{/* --- COLLEGE SECTION --- */} {/* --- COLLEGE SECTION --- */}
{inOrPlanningCollege && ( {inOrPlanningCollege && (
<div className="p-4 border rounded-md space-y-2"> <div className="p-4 border rounded-md space-y-2">
<h3 className="text-xl font-semibold">College Info</h3> <h3 className="text-xl font-semibold">College Info</h3>
<div><strong>College Name:</strong> {collegeData.college_name || 'N/A'}</div>
<div><strong>Major:</strong> {collegeData.major || 'N/A'}</div> <div><strong>College Name</strong></div>
{/* If you have these fields, show them if they're meaningful */} <div><strong>Major</strong></div>
{collegeData.tuition != null && ( <div><strong>Program Type</strong></div>
<div><strong>Tuition (calculated):</strong> {formatNum(collegeData.tuition)}</div> <div><strong>Tuition (calculated)</strong></div>
)} <div><strong>Program Length (years)</strong></div>
{collegeData.program_length != null && ( <div><strong>Credit Hours Per Year</strong></div>
<div><strong>Program Length (years):</strong> {formatNum(collegeData.program_length)}</div> <div><strong>Credit Hours Required</strong></div>
)} <div><strong>Hours Completed</strong></div>
</div> <div><strong>Is In State?</strong></div>
)} <div><strong>Loan Deferral Until Graduation?</strong></div>
<div><strong>Annual Financial Aid</strong></div>
<div><strong>Existing College Debt</strong></div>
<div><strong>Extra Monthly Payment</strong></div>
<div><strong>Expected Graduation</strong></div>
<div><strong>Expected Salary</strong></div>
</div>
)}
{/* --- ACTION BUTTONS --- */} {/* --- ACTION BUTTONS --- */}
<div className="flex justify-between pt-4"> <div className="flex justify-between pt-4">