From 6f3cd163431fecfa126f740ebfec1b5c5c74f5b7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 7 Nov 2022 14:38:42 -0300 Subject: [PATCH] standard Amount field and add more validation (neg values) --- .../src/components/AmountField.tsx | 67 +++++++++++++++++++ .../src/mui/TextField.tsx | 2 + .../src/mui/handlers.ts | 2 +- .../src/mui/input/InputBase.tsx | 30 ++++++++- .../src/wallet/CreateManualWithdraw.test.ts | 4 ++ .../src/wallet/CreateManualWithdraw.tsx | 2 +- .../src/wallet/DepositPage/state.ts | 11 ++- .../src/wallet/DepositPage/test.ts | 36 ++++++++++ .../src/wallet/DepositPage/views.tsx | 65 +++++++----------- .../src/wallet/DestinationSelection.tsx | 48 ++++--------- .../src/wallet/ManageAccount/views.tsx | 19 ++++-- 11 files changed, 203 insertions(+), 83 deletions(-) create mode 100644 packages/taler-wallet-webextension/src/components/AmountField.tsx diff --git a/packages/taler-wallet-webextension/src/components/AmountField.tsx b/packages/taler-wallet-webextension/src/components/AmountField.tsx new file mode 100644 index 000000000..79c510d2f --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/AmountField.tsx @@ -0,0 +1,67 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +import { Fragment, h, VNode } from "preact"; +import { TextFieldHandler } from "../mui/handlers.js"; +import { TextField } from "../mui/TextField.js"; +import { ErrorText } from "./styled/index.js"; + +export function AmountField({ + label, + handler, + currency, + required, +}: { + label: VNode; + required?: boolean; + currency: string; + handler: TextFieldHandler; +}): VNode { + function positiveAmount(value: string): string { + if (!value) return ""; + try { + const num = Number.parseFloat(value); + if (Number.isNaN(num) || num < 0) return handler.value; + if (handler.onInput) { + handler.onInput(value); + } + return value; + } catch (e) { + // do nothing + } + return handler.value; + } + return ( + + {currency} + } + value={handler.value} + disabled={!handler.onInput} + onInput={positiveAmount} + /> + {handler.error && {handler.error}} + + ); +} diff --git a/packages/taler-wallet-webextension/src/mui/TextField.tsx b/packages/taler-wallet-webextension/src/mui/TextField.tsx index 1c1f5cc49..7c6eb40a2 100644 --- a/packages/taler-wallet-webextension/src/mui/TextField.tsx +++ b/packages/taler-wallet-webextension/src/mui/TextField.tsx @@ -40,7 +40,9 @@ export interface Props { minRows?: number; multiline?: boolean; onChange?: (s: string) => void; + onInput?: (s: string) => string; min?: string; + step?: string; placeholder?: string; required?: boolean; diff --git a/packages/taler-wallet-webextension/src/mui/handlers.ts b/packages/taler-wallet-webextension/src/mui/handlers.ts index aa66e75a8..9d393e5b7 100644 --- a/packages/taler-wallet-webextension/src/mui/handlers.ts +++ b/packages/taler-wallet-webextension/src/mui/handlers.ts @@ -16,7 +16,7 @@ import { TalerError } from "@gnu-taler/taler-wallet-core"; export interface TextFieldHandler { - onInput: (value: string) => Promise; + onInput?: (value: string) => Promise; value: string; error?: string; } diff --git a/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx b/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx index 80f5bd44e..e1c6e7af1 100644 --- a/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx +++ b/packages/taler-wallet-webextension/src/mui/input/InputBase.tsx @@ -189,6 +189,7 @@ export function InputBase({ Root = InputBaseRoot, Input, onChange, + onInput, name, placeholder, readOnly, @@ -254,6 +255,19 @@ export function InputBase({ } }; + const handleInput = ( + event: JSX.TargetedEvent, + ): void => { + // if (inputPropsProp.onChange) { + // inputPropsProp.onChange(event, ...args); + // } + + // Perform in the willUpdate + if (onInput) { + event.currentTarget.value = onInput(event.currentTarget.value); + } + }; + const handleClick = ( event: JSX.TargetedMouseEvent, ): void => { @@ -290,6 +304,7 @@ export function InputBase({ onKeyDown={onKeyDown} onKeyUp={onKeyUp} type={type} + onInput={handleInput} onChange={handleChange} onBlur={handleBlur} onFocus={handleFocus} @@ -345,6 +360,7 @@ export function TextareaAutoSize({ // disabled, // size, onChange, + onInput, value, multiline, focused, @@ -480,7 +496,18 @@ export function TextareaAutoSize({ } if (onChange) { - onChange(event); + onChange(event.target.value); + } + }; + const handleInput = (event: any): void => { + renders.current = 0; + + if (!isControlled) { + syncHeight(); + } + + if (onInput) { + event.currentTarget.value = onInput(event.currentTarget.value); } }; @@ -498,6 +525,7 @@ export function TextareaAutoSize({ ].join(" ")} value={value} onChange={handleChange} + onInput={handleInput} ref={inputRef} // Apply the rows prop to get a "correct" first SSR paint rows={minRows} diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts index c757610fc..37c50285b 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts @@ -129,6 +129,8 @@ describe("CreateManualWithdraw states", () => { expect(parsedAmount).equal(undefined); + expect(amount.onInput).not.undefined; + if (!amount.onInput) return; amount.onInput("12"); } @@ -188,6 +190,8 @@ async function defaultTestForInputText( const field = getField(); const initialValue = field.value; nextValue = `${initialValue} something else`; + expect(field.onInput).not.undefined; + if (!field.onInput) return; field.onInput(nextValue); } diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx index 5320c6fe2..dd80faccd 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx @@ -260,7 +260,7 @@ export function CreateManualWithdraw({ state.amount.onInput(e.currentTarget.value)} + // onInput={(e) => state.amount.onInput(e.currentTarget.value)} /> diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts index 2693db79e..d8b752d44 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts @@ -49,7 +49,6 @@ export function useComponentState( parsed !== undefined ? Amounts.stringifyValue(parsed) : "0"; // const [accountIdx, setAccountIdx] = useState(0); const [amount, setAmount] = useState(initialValue); - const [selectedAccount, setSelectedAccount] = useState(); const [fee, setFee] = useState(undefined); @@ -124,6 +123,16 @@ export function useComponentState( const firstAccount = accounts[0].uri const currentAccount = !selectedAccount ? firstAccount : selectedAccount; + if (fee === undefined && parsedAmount) { + getFeeForAmount(currentAccount, parsedAmount, api).then(initialFee => { + setFee(initialFee) + }) + return { + status: "loading", + error: undefined, + }; + } + const accountMap = createLabelsForBankAccount(accounts); async function updateAccountFromList(accountStr: string): Promise { diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts index 9f336ac1a..17e17d185 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts @@ -167,6 +167,11 @@ describe("DepositPage states", () => { accounts: [ibanPayto], }, ); + handler.addWalletCallResponse( + WalletApiOperation.GetFeeForDeposit, + undefined, + withoutFee(), + ); const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState(props, mock)); @@ -176,6 +181,11 @@ describe("DepositPage states", () => { expect(status).equal("loading"); } + expect(await waitForStateUpdate()).true; + { + const { status } = pullLastResultOrThrow(); + expect(status).equal("loading"); + } expect(await waitForStateUpdate()).true; { @@ -214,6 +224,12 @@ describe("DepositPage states", () => { accounts: [talerBankPayto, ibanPayto], }, ); + handler.addWalletCallResponse( + WalletApiOperation.GetFeeForDeposit, + undefined, + withoutFee(), + ); + handler.addWalletCallResponse( WalletApiOperation.GetFeeForDeposit, undefined, @@ -238,6 +254,12 @@ describe("DepositPage states", () => { expect(status).equal("loading"); } + expect(await waitForStateUpdate()).true; + { + const { status } = pullLastResultOrThrow(); + expect(status).equal("loading"); + } + expect(await waitForStateUpdate()).true; const accountSelected = stringifyPaytoUri(ibanPayto.uri); @@ -361,6 +383,11 @@ describe("DepositPage states", () => { accounts: [talerBankPayto, ibanPayto], }, ); + handler.addWalletCallResponse( + WalletApiOperation.GetFeeForDeposit, + undefined, + withoutFee(), + ); handler.addWalletCallResponse( WalletApiOperation.GetFeeForDeposit, undefined, @@ -380,6 +407,13 @@ describe("DepositPage states", () => { expect(status).equal("loading"); } + expect(await waitForStateUpdate()).true; + + { + const { status } = pullLastResultOrThrow(); + expect(status).equal("loading"); + } + expect(await waitForStateUpdate()).true; const accountSelected = stringifyPaytoUri(ibanPayto.uri); @@ -409,6 +443,8 @@ describe("DepositPage states", () => { expect(r.depositHandler.onClick).undefined; expect(r.totalFee).deep.eq(Amounts.parseOrThrow(`${currency}:3`)); + expect(r.amount.onInput).not.undefined; + if (!r.amount.onInput) return; r.amount.onInput("10"); } diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx index e864c8413..771db828d 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx @@ -16,6 +16,7 @@ import { Amounts, PaytoUri } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; +import { AmountField } from "../../components/AmountField.js"; import { ErrorMessage } from "../../components/ErrorMessage.js"; import { LoadingError } from "../../components/LoadingError.js"; import { SelectList } from "../../components/SelectList.js"; @@ -28,6 +29,7 @@ import { } from "../../components/styled/index.js"; import { useTranslationContext } from "../../context/translation.js"; import { Button } from "../../mui/Button.js"; +import { Grid } from "../../mui/Grid.js"; import { State } from "./index.js"; export function LoadingErrorView({ error }: State.LoadingUriError): VNode { @@ -167,48 +169,33 @@ export function ReadyView(state: State.Ready): VNode {

- - -
- {state.currency} - state.amount.onInput(e.currentTarget.value)} + + + Amount} + currency={state.currency} + handler={state.amount} /> -
- {state.amount.error && {state.amount.error}} -
- - - -
- {state.currency} - + + Deposit fee} + currency={state.currency} + handler={{ + value: Amounts.stringifyValue(state.totalFee), + }} /> -
-
- - - -
- {state.currency} - + + Total deposit} + currency={state.currency} + handler={{ + value: Amounts.stringifyValue(state.totalToDeposit), + }} /> -
-
+ +