Change add funds input to number type that only accepts integers (#7628)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Robert Brennan
2025-04-17 14:56:23 -04:00
committed by GitHub
parent 9cbed8802f
commit fedd517a71
5 changed files with 48 additions and 16 deletions

View File

@@ -61,25 +61,25 @@ describe("PaymentForm", () => {
renderPaymentForm();
const topUpInput = await screen.findByTestId("top-up-input");
await user.type(topUpInput, "50.12");
await user.type(topUpInput, "50");
const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT");
await user.click(topUpButton);
expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50.12);
expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50);
});
it("should round the top-up amount to two decimal places", async () => {
it("should only accept integer values", async () => {
const user = userEvent.setup();
renderPaymentForm();
const topUpInput = await screen.findByTestId("top-up-input");
await user.type(topUpInput, "50.125456");
await user.type(topUpInput, "50");
const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT");
await user.click(topUpButton);
expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50.13);
expect(createCheckoutSessionSpy).toHaveBeenCalledWith(50);
});
it("should disable the top-up button if the user enters an invalid amount", async () => {
@@ -100,7 +100,7 @@ describe("PaymentForm", () => {
renderPaymentForm();
const topUpInput = await screen.findByTestId("top-up-input");
await user.type(topUpInput, "50.12");
await user.type(topUpInput, "50");
const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT");
await user.click(topUpButton);
@@ -114,7 +114,7 @@ describe("PaymentForm", () => {
renderPaymentForm();
const topUpInput = await screen.findByTestId("top-up-input");
await user.type(topUpInput, "-50.12");
await user.type(topUpInput, "-50");
const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT");
await user.click(topUpButton);
@@ -139,6 +139,8 @@ describe("PaymentForm", () => {
const user = userEvent.setup();
renderPaymentForm();
// With type="number", the browser would prevent non-numeric input,
// but we'll test the validation logic anyway
const topUpInput = await screen.findByTestId("top-up-input");
await user.type(topUpInput, "abc");
@@ -160,5 +162,19 @@ describe("PaymentForm", () => {
expect(createCheckoutSessionSpy).not.toHaveBeenCalled();
});
test("user enters a decimal value", async () => {
const user = userEvent.setup();
renderPaymentForm();
// With step="1", the browser would validate this, but we'll test our validation logic
const topUpInput = await screen.findByTestId("top-up-input");
await user.type(topUpInput, "50.5");
const topUpButton = screen.getByText("PAYMENT$ADD_CREDIT");
await user.click(topUpButton);
expect(createCheckoutSessionSpy).not.toHaveBeenCalled();
});
});
});

View File

@@ -23,8 +23,8 @@ export function PaymentForm() {
if (amount?.trim()) {
if (!amountIsValid(amount)) return;
const float = parseFloat(amount);
addBalance({ amount: Number(float.toFixed(2)) });
const intValue = parseInt(amount, 10);
addBalance({ amount: intValue });
}
setButtonIsDisabled(true);
@@ -65,10 +65,13 @@ export function PaymentForm() {
testId="top-up-input"
name="top-up-input"
onChange={handleTopUpInputChange}
type="text"
type="number"
label={t(I18nKey.PAYMENT$ADD_FUNDS)}
placeholder="Specify an amount in USD to add - min $10"
className="w-[680px]"
min={10}
max={25000}
step={1}
/>
<div className="flex items-center w-[680px] gap-2">

View File

@@ -13,6 +13,9 @@ interface SettingsInputProps {
startContent?: React.ReactNode;
className?: string;
onChange?: (value: string) => void;
min?: number;
max?: number;
step?: number;
}
export function SettingsInput({
@@ -27,6 +30,9 @@ export function SettingsInput({
startContent,
className,
onChange,
min,
max,
step,
}: SettingsInputProps) {
return (
<label className={cn("flex flex-col gap-2.5 w-fit", className)}>
@@ -43,6 +49,9 @@ export function SettingsInput({
type={type}
defaultValue={defaultValue}
placeholder={placeholder}
min={min}
max={max}
step={step}
className={cn(
"bg-tertiary border border-[#717888] h-10 w-full rounded p-2 placeholder:italic placeholder:text-tertiary-alt",
"disabled:bg-[#2D2F36] disabled:border-[#2D2F36] disabled:cursor-not-allowed",

View File

@@ -4,6 +4,9 @@ import { retrieveAxiosErrorMessage } from "#/utils/retrieve-axios-error-message"
import { useGetGitChanges } from "#/hooks/query/use-get-git-changes";
import { I18nKey } from "#/i18n/declaration";
// Error message patterns
const GIT_REPO_ERROR_PATTERN = /not a git repository/i;
function StatusMessage({ children }: React.PropsWithChildren) {
return (
<div className="w-full h-full flex items-center text-center justify-center text-2xl text-tertiary-light">
@@ -17,7 +20,7 @@ function EditorScreen() {
const { data: gitChanges, isSuccess, isError, error } = useGetGitChanges();
const isNotGitRepoError =
error && retrieveAxiosErrorMessage(error) === "Not a git repository";
error && GIT_REPO_ERROR_PATTERN.test(retrieveAxiosErrorMessage(error));
return (
<main className="h-full overflow-y-scroll px-4 py-3 gap-3 flex flex-col">

View File

@@ -2,11 +2,12 @@ const MINIMUM_AMOUNT = 10;
const MAXIMUM_AMOUNT = 25_000;
export const amountIsValid = (amount: string) => {
const float = parseFloat(amount);
if (Number.isNaN(float)) return false;
if (float < 0) return false;
if (float < MINIMUM_AMOUNT) return false;
if (float > MAXIMUM_AMOUNT) return false;
const value = parseInt(amount, 10);
if (Number.isNaN(value)) return false;
if (value < 0) return false;
if (value < MINIMUM_AMOUNT) return false;
if (value > MAXIMUM_AMOUNT) return false;
if (value !== parseFloat(amount)) return false; // Ensure it's an integer
return true;
};