summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/taler-wallet-webextension/src/components/AmountField.tsx67
-rw-r--r--packages/taler-wallet-webextension/src/mui/TextField.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/mui/handlers.ts2
-rw-r--r--packages/taler-wallet-webextension/src/mui/input/InputBase.tsx30
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.test.ts4
-rw-r--r--packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/state.ts11
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/test.ts36
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DepositPage/views.tsx65
-rw-r--r--packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx48
-rw-r--r--packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx19
11 files changed, 203 insertions, 83 deletions
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 <http://www.gnu.org/licenses/>
+ */
+
+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 (
+ <Fragment>
+ <TextField
+ label={label}
+ type="number"
+ min="0"
+ step="0.1"
+ variant="filled"
+ error={!!handler.error}
+ required={required}
+ startAdornment={
+ <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
+ }
+ value={handler.value}
+ disabled={!handler.onInput}
+ onInput={positiveAmount}
+ />
+ {handler.error && <ErrorText>{handler.error}</ErrorText>}
+ </Fragment>
+ );
+}
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<void>;
+ onInput?: (value: string) => Promise<void>;
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<HTMLElement & { value?: string }>,
+ ): 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<HTMLElement & { value?: string }>,
): 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({
<input
type="number"
value={state.amount.value}
- onInput={(e) => state.amount.onInput(e.currentTarget.value)}
+ // onInput={(e) => state.amount.onInput(e.currentTarget.value)}
/>
</div>
</InputWithLabel>
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<number>(0);
const [amount, setAmount] = useState(initialValue);
-
const [selectedAccount, setSelectedAccount] = useState<PaytoUri>();
const [fee, setFee] = useState<DepositGroupFees | undefined>(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<void> {
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));
@@ -177,6 +182,11 @@ describe("DepositPage states", () => {
}
expect(await waitForStateUpdate()).true;
+ {
+ const { status } = pullLastResultOrThrow();
+ expect(status).equal("loading");
+ }
+ expect(await waitForStateUpdate()).true;
{
const r = pullLastResultOrThrow();
@@ -219,6 +229,12 @@ describe("DepositPage states", () => {
undefined,
withoutFee(),
);
+
+ handler.addWalletCallResponse(
+ WalletApiOperation.GetFeeForDeposit,
+ undefined,
+ withoutFee(),
+ );
handler.addWalletCallResponse(
WalletApiOperation.GetFeeForDeposit,
undefined,
@@ -239,6 +255,12 @@ describe("DepositPage states", () => {
}
expect(await waitForStateUpdate()).true;
+ {
+ const { status } = pullLastResultOrThrow();
+ expect(status).equal("loading");
+ }
+
+ expect(await waitForStateUpdate()).true;
const accountSelected = stringifyPaytoUri(ibanPayto.uri);
{
@@ -364,6 +386,11 @@ describe("DepositPage states", () => {
handler.addWalletCallResponse(
WalletApiOperation.GetFeeForDeposit,
undefined,
+ withoutFee(),
+ );
+ handler.addWalletCallResponse(
+ WalletApiOperation.GetFeeForDeposit,
+ undefined,
withSomeFee(),
);
handler.addWalletCallResponse(
@@ -381,6 +408,13 @@ describe("DepositPage states", () => {
}
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 {
<p>
<AccountDetails account={state.currentAccount} />
</p>
- <InputWithLabel invalid={!!state.amount.error}>
- <label>
- <i18n.Translate>Amount</i18n.Translate>
- </label>
- <div>
- <span>{state.currency}</span>
- <input
- type="number"
- value={state.amount.value}
- onInput={(e) => state.amount.onInput(e.currentTarget.value)}
+ <Grid container spacing={2} columns={1}>
+ <Grid item xs={1}>
+ <AmountField
+ label={<i18n.Translate>Amount</i18n.Translate>}
+ currency={state.currency}
+ handler={state.amount}
/>
- </div>
- {state.amount.error && <ErrorText>{state.amount.error}</ErrorText>}
- </InputWithLabel>
-
- <InputWithLabel>
- <label>
- <i18n.Translate>Deposit fee</i18n.Translate>
- </label>
- <div>
- <span>{state.currency}</span>
- <input
- type="number"
- disabled
- value={Amounts.stringifyValue(state.totalFee)}
+ </Grid>
+ <Grid item xs={1}>
+ <AmountField
+ label={<i18n.Translate>Deposit fee</i18n.Translate>}
+ currency={state.currency}
+ handler={{
+ value: Amounts.stringifyValue(state.totalFee),
+ }}
/>
- </div>
- </InputWithLabel>
-
- <InputWithLabel>
- <label>
- <i18n.Translate>Total deposit</i18n.Translate>
- </label>
- <div>
- <span>{state.currency}</span>
- <input
- type="number"
- disabled
- value={Amounts.stringifyValue(state.totalToDeposit)}
+ </Grid>
+ <Grid item xs={1}>
+ <AmountField
+ label={<i18n.Translate>Total deposit</i18n.Translate>}
+ currency={state.currency}
+ handler={{
+ value: Amounts.stringifyValue(state.totalToDeposit),
+ }}
/>
- </div>
- </InputWithLabel>
+ </Grid>
+ </Grid>
</section>
<footer>
<Button
diff --git a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
index c584f2aae..ba1a560ef 100644
--- a/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/DestinationSelection.tsx
@@ -19,6 +19,7 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { styled } from "@linaria/react";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
+import { AmountField } from "../components/AmountField.js";
import { Loading } from "../components/Loading.js";
import { LoadingError } from "../components/LoadingError.js";
import { SelectList } from "../components/SelectList.js";
@@ -32,6 +33,7 @@ import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
import { Grid } from "../mui/Grid.js";
+import { TextFieldHandler } from "../mui/handlers.js";
import { Paper } from "../mui/Paper.js";
import { TextField } from "../mui/TextField.js";
import { Pages } from "../NavigationBar.js";
@@ -283,11 +285,6 @@ export function DestinationSelectionGetCash({
const [currency, setCurrency] = useState(parsedInitialAmount?.currency);
const [amount, setAmount] = useState(parsedInitialAmountValue);
- function positiveSetAmount(e: string):void {
- const value = Number.parseInt(e, 10);
- if (value < 0) return
- setAmount(String(value))
- }
const { i18n } = useTranslationContext();
const previous1: Contact[] = [];
const previous2: Contact[] = [
@@ -326,19 +323,13 @@ export function DestinationSelectionGetCash({
<i18n.Translate>Specify the amount and the origin</i18n.Translate>
</h1>
<Grid container columns={2} justifyContent="space-between">
- <TextField
- label="Amount"
- type="number"
- min="0"
- variant="filled"
- error={invalid}
+ <AmountField
+ label={<i18n.Translate>Amount</i18n.Translate>}
+ currency={currency}
required
- startAdornment={
- <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
- }
- value={amount}
- onChange={(e) => {
- setAmount(e);
+ handler={{
+ onInput: async (s) => setAmount(s),
+ value: amount,
}}
/>
<Button onClick={async () => setCurrency(undefined)}>
@@ -431,11 +422,6 @@ export function DestinationSelectionSendCash({
const currency = parsedInitialAmount?.currency;
const [amount, setAmount] = useState(parsedInitialAmountValue);
- function positiveSetAmount(e: string):void {
- const value = Number.parseInt(e, 10);
- if (value < 0) return
- setAmount(String(value))
- }
const { i18n } = useTranslationContext();
const previous1: Contact[] = [];
const previous2: Contact[] = [
@@ -474,19 +460,13 @@ export function DestinationSelectionSendCash({
</h1>
<div>
- <TextField
- label="Amount"
- type="number"
- min="0"
- variant="filled"
+ <AmountField
+ label={<i18n.Translate>Amount</i18n.Translate>}
+ currency={currency}
required
- error={invalid}
- startAdornment={
- <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
- }
- value={amount}
- onChange={(e) => {
- positiveSetAmount(e);
+ handler={{
+ onInput: async (s) => setAmount(s),
+ value: amount,
}}
/>
</div>
diff --git a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx
index 832ca91b7..326e078f4 100644
--- a/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/ManageAccount/views.tsx
@@ -416,9 +416,10 @@ function BitcoinAddressAccount({ field }: { field: TextFieldHandler }): VNode {
fullWidth
value={value}
error={value !== undefined && !!errors?.value}
+ disabled={!field.onInput}
onChange={(v) => {
setValue(v);
- if (!errors) {
+ if (!errors && field.onInput) {
field.onInput(`payto://bitcoin/${v}`);
}
}}
@@ -456,9 +457,10 @@ function TalerBankAddressAccount({
fullWidth
value={host}
error={host !== undefined && !!errors?.host}
+ disabled={!field.onInput}
onChange={(v) => {
setHost(v);
- if (!errors) {
+ if (!errors && field.onInput) {
field.onInput(`payto://x-taler-bank/${v}/${account}`);
}
}}
@@ -470,11 +472,12 @@ function TalerBankAddressAccount({
label="Bank account"
variant="standard"
fullWidth
+ disabled={!field.onInput}
value={account}
error={account !== undefined && !!errors?.account}
onChange={(v) => {
setAccount(v || "");
- if (!errors) {
+ if (!errors && field.onInput) {
field.onInput(`payto://x-taler-bank/${host}/${v}`);
}
}}
@@ -502,9 +505,10 @@ function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode {
fullWidth
value={number}
error={number !== undefined && !!errors?.number}
+ disabled={!field.onInput}
onChange={(v) => {
setNumber(v);
- if (!errors) {
+ if (!errors && field.onInput) {
field.onInput(`payto://iban/${v}?receiver-name=${name}`);
}
}}
@@ -518,10 +522,13 @@ function IbanAddressAccount({ field }: { field: TextFieldHandler }): VNode {
fullWidth
value={name}
error={name !== undefined && !!errors?.name}
+ disabled={!field.onInput}
onChange={(v) => {
setName(v);
- if (!errors) {
- field.onInput(`payto://iban/${number}?receiver-name=${encodeURIComponent(v)}`);
+ if (!errors && field.onInput) {
+ field.onInput(
+ `payto://iban/${number}?receiver-name=${encodeURIComponent(v)}`,
+ );
}
}}
/>