fixed inputs to accept decimals
All checks were successful
Deploy / deploy (push) Successful in 59s
Security Tests / security-non-db (push) Successful in 18s
Security Tests / security-db (push) Successful in 24s

This commit is contained in:
2026-03-11 20:02:32 -05:00
parent 72334b2583
commit cccce2c854
2 changed files with 51 additions and 11 deletions

View File

@@ -75,6 +75,14 @@ const defaultSchedule = (): AutoPaySchedule => ({
const DEFAULT_SAVINGS_PERCENT = 20;
const MIN_SAVINGS_PERCENT = DEFAULT_SAVINGS_PERCENT;
const normalizeCategoryName = (value: string) => value.trim().toLowerCase();
const normalizePercentValue = (value: unknown) => {
const parsed =
typeof value === "number"
? value
: Number.parseFloat(String(value ?? ""));
if (!Number.isFinite(parsed)) return 0;
return Math.max(0, Math.min(100, Math.round(parsed)));
};
const createDefaultSavingsCategory = (): VariableCat => ({
id: crypto.randomUUID(),
name: "savings",
@@ -136,7 +144,13 @@ export default function OnboardingPage() {
if (s.budgetConservatism) setBudgetConservatism(s.budgetConservatism);
if (Number.isFinite(s.customFixedPercentage)) setCustomFixedPercentage(s.customFixedPercentage);
if (Array.isArray(s.vars) && s.vars.length > 0) {
setVars(s.vars.map((v: VariableCat) => ({ ...v, name: normalizeCategoryName(v.name) })));
setVars(
s.vars.map((v: VariableCat) => ({
...v,
name: normalizeCategoryName(v.name ?? ""),
percent: normalizePercentValue(v.percent),
}))
);
}
if (Array.isArray(s.fixeds))
setFixeds(
@@ -181,11 +195,15 @@ export default function OnboardingPage() {
// ── computed
const varsTotal = useMemo(
() => vars.reduce((s, v) => s + (v.percent || 0), 0),
() => vars.reduce((s, v) => s + normalizePercentValue(v.percent), 0),
[vars]
);
const savingsTotal = useMemo(
() => vars.reduce((s, v) => s + (v.isSavings ? v.percent || 0 : 0), 0),
() =>
vars.reduce(
(s, v) => s + (v.isSavings ? normalizePercentValue(v.percent) : 0),
0
),
[vars]
);
const varsRemaining = Math.max(0, 100 - varsTotal);
@@ -200,6 +218,10 @@ export default function OnboardingPage() {
.filter(([, count]) => count > 1)
.map(([name]) => name);
}, [vars]);
const hasMissingVarNames = useMemo(
() => vars.some((v) => !normalizeCategoryName(v.name)),
[vars]
);
const fixedTotal = useMemo(
() => fixeds.reduce((s, f) => s + (f.amountCents || 0), 0),
[fixeds]
@@ -241,7 +263,7 @@ export default function OnboardingPage() {
const canNext4 =
vars.length > 0 &&
varsTotal === 100 &&
vars.every((v) => v.name.trim()) &&
!hasMissingVarNames &&
vars.some((v) => v.isSavings) &&
savingsTotal >= MIN_SAVINGS_PERCENT &&
duplicateVarNames.length === 0;
@@ -520,7 +542,7 @@ export default function OnboardingPage() {
dashboardSnapshot.variableCategories.map((c, i) => ({
id: c.id,
name: normalizeCategoryName(c.name),
percent: c.percent,
percent: normalizePercentValue(c.percent),
priority: i + 1,
isSavings: !!c.isSavings,
}))
@@ -943,6 +965,12 @@ export default function OnboardingPage() {
</p>
</div>
)}
{hasMissingVarNames && (
<div className="toast-err">
Every category needs a name before you can continue.
</div>
)}
{varsTotal !== 100 && varsTotal > 0 && (
<div className="toast-err">