diff --git a/.build.hash b/.build.hash index f5825b3..df5c75a 100644 --- a/.build.hash +++ b/.build.hash @@ -1 +1 @@ -408b293acaaa053b934050f88b9c93db41ecb097-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b +7c4503634c1566a112e17705d07e15f792647175-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b diff --git a/backend/server1.js b/backend/server1.js index 6eec2a6..9335554 100755 --- a/backend/server1.js +++ b/backend/server1.js @@ -27,7 +27,7 @@ const CANARY_SQL = ` const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const rootPath = path.resolve(__dirname, '..'); -const isProd = (process.env.ENV_NAME === 'prod'); +const env = (process.env.ENV_NAME === 'prod'); const envPath = path.resolve(rootPath, `.env.${env}`); dotenv.config({ path: envPath, override: false }); diff --git a/backend/server2.js b/backend/server2.js index 1a02f84..58ef82b 100755 --- a/backend/server2.js +++ b/backend/server2.js @@ -32,7 +32,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const rootPath = path.resolve(__dirname, '..'); -const isProd = (process.env.ENV_NAME === 'prod'); +const env = (process.env.ENV_NAME === 'prod'); const envPath = path.resolve(rootPath, `.env.${env}`); dotenv.config({ path: envPath, override: false }); // don't clobber compose-injected env diff --git a/backend/server3.js b/backend/server3.js index 11677f5..dbffa5d 100644 --- a/backend/server3.js +++ b/backend/server3.js @@ -3435,6 +3435,30 @@ app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req, extra_cash_retirement_pct } = req.body; + // If the payload is empty, do not clobber existing values + if (!req.body || Object.keys(req.body).length === 0) { + return res.json({ message: 'No changes' }); + } + + // ---- Normalize split: numbers, clamp, complement, 50/50 fallback ---- + function normalizeSplit(eIn, rIn) { + let e = Number(eIn), r = Number(rIn); + const finiteE = Number.isFinite(e), finiteR = Number.isFinite(r); + if (!finiteE && !finiteR) return { e: 50, r: 50 }; + if (finiteE && !finiteR) { e = Math.min(Math.max(e, 0), 100); return { e, r: 100 - e }; } + if (!finiteE && finiteR) { r = Math.min(Math.max(r, 0), 100); return { e: 100 - r, r }; } + // both finite + e = Math.min(Math.max(e, 0), 100); + r = Math.min(Math.max(r, 0), 100); + if (e + r === 0) return { e: 50, r: 50 }; + if (e + r === 100) return { e, r }; + // scale to sum 100 to preserve proportion + const sum = e + r; + return { e: (e / sum) * 100, r: (r / sum) * 100 }; + } + const { e: ePct, r: rPct } = normalizeSplit(extra_cash_emergency_pct, extra_cash_retirement_pct); + + try { // see if profile exists const [existingRows] = await pool.query(` diff --git a/src/components/CareerProfileList.js b/src/components/CareerProfileList.js index 86b5115..bd8a291 100644 --- a/src/components/CareerProfileList.js +++ b/src/components/CareerProfileList.js @@ -25,33 +25,21 @@ const nav = useNavigate(); async function remove(row) { if (!window.confirm('Delete this career profile?')) return; try { - const r = await apiFetch(`/api/premium/career-profile/by-fields`, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - scenario_title: row.scenario_title || null, - career_name : row.career_name || null, - start_date : row.start_date || null - }) + const r = await apiFetch(`/api/premium/career-profile/${encodeURIComponent(row.id)}`, { + method: 'DELETE' }); if (!r.ok) { - // 401/403 will already be handled by apiFetch + // 401/403 handled by apiFetch const msg = await r.text().catch(() => 'Failed to delete'); alert(msg || 'Failed to delete'); return; } - setRows(prev => prev.filter(x => - !( - (x.scenario_title || '') === (row.scenario_title || '') && - (x.career_name || '') === (row.career_name || '') && - (x.start_date || '') === (row.start_date || '') - ) - )); + setRows(prev => prev.filter(x => x.id !== row.id)); } catch (e) { console.error('Delete failed:', e); alert('Failed to delete'); } - } + } return (
diff --git a/src/components/PremiumOnboarding/FinancialOnboarding.js b/src/components/PremiumOnboarding/FinancialOnboarding.js index 2b75be5..aff65a7 100644 --- a/src/components/PremiumOnboarding/FinancialOnboarding.js +++ b/src/components/PremiumOnboarding/FinancialOnboarding.js @@ -69,8 +69,8 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => { })); saveDraft({ financialData: { - extra_cash_emergency_pct: val, - extra_cash_retirement_pct: 100 - val + extra_cash_retirement_pct: val, + extra_cash_emergency_pct: 100 - val } }).catch(() => {}); } else { @@ -82,16 +82,36 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => { extra_cash_retirement_pct: Number.isFinite(extra_cash_retirement_pct) ? extra_cash_retirement_pct : 50 } }).catch(()=>{}); + setData(prev => ({ ...prev, [name]: val })); + // Persist with 50/50 fallback if split is invalid or both 0 + let ePct = Number(extra_cash_emergency_pct); + let rPct = Number(extra_cash_retirement_pct); + if (!Number.isFinite(ePct)) ePct = 0; + if (!Number.isFinite(rPct)) rPct = 0; + if ((ePct + rPct) === 0) { ePct = 50; rPct = 50; } + saveDraft({ financialData: { + [name]: val, + extra_cash_emergency_pct: ePct, + extra_cash_retirement_pct: rPct + }}).catch(()=>{}); } }; const handleSubmit = () => { - saveDraft({ financialData: { - extra_cash_emergency_pct: Number.isFinite(extra_cash_emergency_pct) ? extra_cash_emergency_pct : 50, - extra_cash_retirement_pct: Number.isFinite(extra_cash_retirement_pct) ? extra_cash_retirement_pct : 50 - }}).catch(()=>{}); - nextStep(); -}; + // Final guard: coerce to numbers, clamp, and 50/50 if both resolve to 0 + let ePct = Number(extra_cash_emergency_pct); + let rPct = Number(extra_cash_retirement_pct); + if (!Number.isFinite(ePct)) ePct = 0; + if (!Number.isFinite(rPct)) rPct = 0; + ePct = Math.min(Math.max(ePct, 0), 100); + rPct = Math.min(Math.max(rPct, 0), 100); + if ((ePct + rPct) === 0) { ePct = 50; rPct = 50; } + saveDraft({ financialData: { + extra_cash_emergency_pct: ePct, + extra_cash_retirement_pct: rPct + }}).catch(()=>{}); + nextStep(); + }; return (
@@ -269,7 +289,7 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => { value={extra_cash_emergency_pct} onChange={handleChange} onBlur={(e) => { - const v = parseFloat(e.target.value) || 0; + const v = parseFloat(e.target.value) || 50; saveDraft({ financialData: { extra_cash_emergency_pct: v } }).catch(() => {}); }} className="w-full border rounded p-2" @@ -285,7 +305,7 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => { value={extra_cash_retirement_pct} onChange={handleChange} onBlur={(e) => { - const v = parseFloat(e.target.value) || 0; + const v = parseFloat(e.target.value) || 50; saveDraft({ financialData: { extra_cash_retirement_pct: v } }).catch(() => {}); }} className="w-full border rounded p-2"