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 __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const rootPath = path.resolve(__dirname, '..');
|
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}`);
|
const envPath = path.resolve(rootPath, `.env.${env}`);
|
||||||
dotenv.config({ path: envPath, override: false });
|
dotenv.config({ path: envPath, override: false });
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const rootPath = path.resolve(__dirname, '..');
|
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}`);
|
const envPath = path.resolve(rootPath, `.env.${env}`);
|
||||||
dotenv.config({ path: envPath, override: false }); // don't clobber compose-injected 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
|
extra_cash_retirement_pct
|
||||||
} = req.body;
|
} = 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 {
|
try {
|
||||||
// see if profile exists
|
// see if profile exists
|
||||||
const [existingRows] = await pool.query(`
|
const [existingRows] = await pool.query(`
|
||||||
|
@ -25,28 +25,16 @@ const nav = useNavigate();
|
|||||||
async function remove(row) {
|
async function remove(row) {
|
||||||
if (!window.confirm('Delete this career profile?')) return;
|
if (!window.confirm('Delete this career profile?')) return;
|
||||||
try {
|
try {
|
||||||
const r = await apiFetch(`/api/premium/career-profile/by-fields`, {
|
const r = await apiFetch(`/api/premium/career-profile/${encodeURIComponent(row.id)}`, {
|
||||||
method: 'DELETE',
|
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
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
if (!r.ok) {
|
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');
|
const msg = await r.text().catch(() => 'Failed to delete');
|
||||||
alert(msg || 'Failed to delete');
|
alert(msg || 'Failed to delete');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setRows(prev => prev.filter(x =>
|
setRows(prev => prev.filter(x => x.id !== row.id));
|
||||||
!(
|
|
||||||
(x.scenario_title || '') === (row.scenario_title || '') &&
|
|
||||||
(x.career_name || '') === (row.career_name || '') &&
|
|
||||||
(x.start_date || '') === (row.start_date || '')
|
|
||||||
)
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Delete failed:', e);
|
console.error('Delete failed:', e);
|
||||||
alert('Failed to delete');
|
alert('Failed to delete');
|
||||||
|
@ -69,8 +69,8 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => {
|
|||||||
}));
|
}));
|
||||||
saveDraft({
|
saveDraft({
|
||||||
financialData: {
|
financialData: {
|
||||||
extra_cash_emergency_pct: val,
|
extra_cash_retirement_pct: val,
|
||||||
extra_cash_retirement_pct: 100 - val
|
extra_cash_emergency_pct: 100 - val
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
} else {
|
} 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
|
extra_cash_retirement_pct: Number.isFinite(extra_cash_retirement_pct) ? extra_cash_retirement_pct : 50
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).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 = () => {
|
const handleSubmit = () => {
|
||||||
saveDraft({ financialData: {
|
// Final guard: coerce to numbers, clamp, and 50/50 if both resolve to 0
|
||||||
extra_cash_emergency_pct: Number.isFinite(extra_cash_emergency_pct) ? extra_cash_emergency_pct : 50,
|
let ePct = Number(extra_cash_emergency_pct);
|
||||||
extra_cash_retirement_pct: Number.isFinite(extra_cash_retirement_pct) ? extra_cash_retirement_pct : 50
|
let rPct = Number(extra_cash_retirement_pct);
|
||||||
}}).catch(()=>{});
|
if (!Number.isFinite(ePct)) ePct = 0;
|
||||||
nextStep();
|
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 (
|
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">
|
||||||
@ -269,7 +289,7 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => {
|
|||||||
value={extra_cash_emergency_pct}
|
value={extra_cash_emergency_pct}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const v = parseFloat(e.target.value) || 0;
|
const v = parseFloat(e.target.value) || 50;
|
||||||
saveDraft({ financialData: { extra_cash_emergency_pct: v } }).catch(() => {});
|
saveDraft({ financialData: { extra_cash_emergency_pct: v } }).catch(() => {});
|
||||||
}}
|
}}
|
||||||
className="w-full border rounded p-2"
|
className="w-full border rounded p-2"
|
||||||
@ -285,7 +305,7 @@ const FinancialOnboarding = ({ nextStep, prevStep, data, setData }) => {
|
|||||||
value={extra_cash_retirement_pct}
|
value={extra_cash_retirement_pct}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const v = parseFloat(e.target.value) || 0;
|
const v = parseFloat(e.target.value) || 50;
|
||||||
saveDraft({ financialData: { extra_cash_retirement_pct: v } }).catch(() => {});
|
saveDraft({ financialData: { extra_cash_retirement_pct: v } }).catch(() => {});
|
||||||
}}
|
}}
|
||||||
className="w-full border rounded p-2"
|
className="w-full border rounded p-2"
|
||||||
|
Loading…
Reference in New Issue
Block a user