mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-10 07:18:10 -05:00
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user