fix(ui): let the per-user Internal Users edit form save sub-cent budgets

The same native step veto lived on the per-user edit form: its Max Budget
field renders through NumericalInput with step={0.01} inside an antd form
that never opted out of constraint validation, so typing 0.001 on a user
and pressing Save Changes did nothing at all; no request, no error, and the
detail view kept showing the previous budget.

The field now uses step="any" and the form carries noValidate, so antd's own
rules stay the only gate. The dead precision={2} prop went with it: it is an
antd InputNumber prop and NumericalInput renders a Tremor NumberInput, so it
only ever leaked to the DOM.
This commit is contained in:
ryan-crabbe-berri 2026-07-30 19:02:30 -07:00
parent 1018d18e6b
commit 28c5b4cdbe
3 changed files with 21 additions and 3 deletions

View file

@ -389,6 +389,24 @@ describe("UserEditView", () => {
expect(callArgs.metadata).toEqual(MOCK_USER_DATA.user_info.metadata);
});
it("should submit a sub-cent max budget the browser would veto under a 0.01 step", async () => {
const onSubmitMock = vi.fn();
renderWithProviders(<UserEditView {...defaultProps} onSubmit={onSubmitMock} />);
const budget: HTMLInputElement = await screen.findByRole("spinbutton");
await userEvent.clear(budget);
await userEvent.type(budget, "0.001");
// jsdom never blocks the submit itself, so assert the constraint the real browser
// enforces before onFinish ever runs
expect(budget.checkValidity()).toBe(true);
await userEvent.click(screen.getByRole("button", { name: /save changes/i }));
await waitFor(() => expect(onSubmitMock).toHaveBeenCalled());
expect(Number(onSubmitMock.mock.calls[0][0].max_budget)).toBe(0.001);
});
it("should set max_budget to null when unlimited budget is checked on submit", async () => {
const onSubmitMock = vi.fn();
const userDataWithNullBudget = {

View file

@ -96,7 +96,7 @@ export function UserEditView({
};
return (
<Form form={form} onFinish={handleSubmit} layout="vertical">
<Form form={form} onFinish={handleSubmit} layout="vertical" noValidate>
{!isBulkEdit && (
<Form.Item label="User ID" name="user_id">
<TextInput disabled />
@ -191,7 +191,7 @@ export function UserEditView({
},
]}
>
<NumericalInput step={0.01} precision={2} style={{ width: "100%" }} disabled={unlimitedBudget} />
<NumericalInput step="any" style={{ width: "100%" }} disabled={unlimitedBudget} />
</Form.Item>
<Form.Item label="Reset Budget" name="budget_duration">

View file

@ -2,7 +2,7 @@ import React from "react";
import { NumberInput } from "@tremor/react";
interface NumericalInputProps {
step?: number;
step?: number | "any";
style?: React.CSSProperties;
placeholder?: string;
min?: number;