fixed career profile delete, simulator 50/50 defaults for pcts
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
This commit is contained in:
parent
20a1f796b5
commit
46b66df823
@ -1 +1 @@
|
||||
408b293acaaa053b934050f88b9c93db41ecb097-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b
|
||||
7c4503634c1566a112e17705d07e15f792647175-372bcf506971f56c4911b429b9f5de5bc37ed008-e9eccd451b778829eb2f2c9752c670b707e1268b
|
||||
|
@ -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 });
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(`
|
||||
|
@ -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 (
|
||||
<div className="max-w-4xl mx-auto space-y-4">
|
||||
|
@ -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 (
|
||||
<div className="max-w-md mx-auto p-6 space-y-6">
|
||||
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user