diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/WalletWithdrawForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/WalletWithdrawForm.tsx | 259 |
1 files changed, 145 insertions, 114 deletions
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index a1b616657..2b2df3baa 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -14,36 +14,54 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Logger } from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { StateUpdater, useEffect, useRef } from "preact/hooks"; -import { useBackendContext } from "../context/backend.js"; -import { PageStateType, usePageContext } from "../context/pageState.js"; +import { Amounts, Logger } from "@gnu-taler/taler-util"; import { - InternationalizationAPI, + RequestError, useTranslationContext, } from "@gnu-taler/web-util/lib/index.browser"; -import { BackendState } from "../hooks/backend.js"; -import { prepareHeaders, validateAmount } from "../utils.js"; +import { h, VNode } from "preact"; +import { useEffect, useRef, useState } from "preact/hooks"; +import { PageStateType, usePageContext } from "../context/pageState.js"; +import { useAccessAPI } from "../hooks/access.js"; +import { undefinedIfEmpty } from "../utils.js"; +import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; const logger = new Logger("WalletWithdrawForm"); export function WalletWithdrawForm({ focus, currency, + onError, + onSuccess, }: { - currency?: string; + currency: string; focus?: boolean; + onError: (e: PageStateType["error"]) => void; + onSuccess: ( + data: SandboxBackend.Access.BankAccountCreateWithdrawalResponse, + ) => void; }): VNode { - const backend = useBackendContext(); - const { pageState, pageStateSetter } = usePageContext(); + // const backend = useBackendContext(); + // const { pageState, pageStateSetter } = usePageContext(); const { i18n } = useTranslationContext(); - let submitAmount: string | undefined = "5.00"; + const { createWithdrawal } = useAccessAPI(); + const [amount, setAmount] = useState<string | undefined>("5.00"); const ref = useRef<HTMLInputElement>(null); useEffect(() => { if (focus) ref.current?.focus(); }, [focus]); + + const amountFloat = amount ? parseFloat(amount) : undefined; + const errors = undefinedIfEmpty({ + amount: !amountFloat + ? i18n.str`required` + : Number.isNaN(amountFloat) + ? i18n.str`should be a number` + : amountFloat < 0 + ? i18n.str`should be positive` + : undefined, + }); return ( <form id="reserve-form" @@ -63,8 +81,8 @@ export function WalletWithdrawForm({ type="text" readonly class="currency-indicator" - size={currency?.length ?? 5} - maxLength={currency?.length} + size={currency.length} + maxLength={currency.length} tabIndex={-1} value={currency} /> @@ -74,14 +92,15 @@ export function WalletWithdrawForm({ ref={ref} id="withdraw-amount" name="withdraw-amount" - value={submitAmount} + value={amount ?? ""} onChange={(e): void => { - // FIXME: validate using 'parseAmount()', - // deactivate submit button as long as - // amount is not valid - submitAmount = e.currentTarget.value; + setAmount(e.currentTarget.value); }} /> + <ShowInputErrorLabel + message={errors?.amount} + isDirty={amount !== undefined} + /> </div> </p> <p> @@ -90,22 +109,34 @@ export function WalletWithdrawForm({ id="select-exchange" class="pure-button pure-button-primary" type="submit" + disabled={!!errors} value={i18n.str`Withdraw`} - onClick={(e) => { + onClick={async (e) => { e.preventDefault(); - submitAmount = validateAmount(submitAmount); - /** - * By invalid amounts, the validator prints error messages - * on the console, and the browser colourizes the amount input - * box to indicate a error. - */ - if (!submitAmount && currency) return; - createWithdrawalCall( - `${currency}:${submitAmount}`, - backend.state, - pageStateSetter, - i18n, - ); + if (!amountFloat) return; + try { + const result = await createWithdrawal({ + amount: Amounts.stringify( + Amounts.fromFloat(amountFloat, currency), + ), + }); + + onSuccess(result.data); + } catch (error) { + if (error instanceof RequestError) { + onError({ + title: i18n.str`Could not create withdrawal operation`, + description: (error as any).error.description, + debug: JSON.stringify(error), + }); + } + if (error instanceof Error) { + onError({ + title: i18n.str`Something when wrong trying to start the withdrawal`, + description: error.message, + }); + } + } }} /> </div> @@ -114,84 +145,84 @@ export function WalletWithdrawForm({ ); } -/** - * This function creates a withdrawal operation via the Access API. - * - * After having successfully created the withdrawal operation, the - * user should receive a QR code of the "taler://withdraw/" type and - * supposed to scan it with their phone. - * - * TODO: (1) after the scan, the page should refresh itself and inform - * the user about the operation's outcome. (2) use POST helper. */ -async function createWithdrawalCall( - amount: string, - backendState: BackendState, - pageStateSetter: StateUpdater<PageStateType>, - i18n: InternationalizationAPI, -): Promise<void> { - if (backendState?.status === "loggedOut") { - logger.error("Page has a problem: no credentials found in the state."); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: i18n.str`No credentials given.`, - }, - })); - return; - } - - let res: Response; - try { - const { username, password } = backendState; - const headers = prepareHeaders(username, password); - - // Let bank generate withdraw URI: - const url = new URL( - `access-api/accounts/${backendState.username}/withdrawals`, - backendState.url, - ); - res = await fetch(url.href, { - method: "POST", - headers, - body: JSON.stringify({ amount }), - }); - } catch (error) { - logger.trace("Could not POST withdrawal request to the bank", error); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: i18n.str`Could not create withdrawal operation`, - description: (error as any).error.description, - debug: JSON.stringify(error), - }, - })); - return; - } - if (!res.ok) { - const response = await res.json(); - logger.error( - `Withdrawal creation gave response error: ${response} (${res.status})`, - ); - pageStateSetter((prevState) => ({ - ...prevState, - - error: { - title: i18n.str`Withdrawal creation gave response error`, - description: response.error.description, - debug: JSON.stringify(response), - }, - })); - return; - } - - logger.trace("Withdrawal operation created!"); - const resp = await res.json(); - pageStateSetter((prevState: PageStateType) => ({ - ...prevState, - withdrawalInProgress: true, - talerWithdrawUri: resp.taler_withdraw_uri, - withdrawalId: resp.withdrawal_id, - })); -} +// /** +// * This function creates a withdrawal operation via the Access API. +// * +// * After having successfully created the withdrawal operation, the +// * user should receive a QR code of the "taler://withdraw/" type and +// * supposed to scan it with their phone. +// * +// * TODO: (1) after the scan, the page should refresh itself and inform +// * the user about the operation's outcome. (2) use POST helper. */ +// async function createWithdrawalCall( +// amount: string, +// backendState: BackendState, +// pageStateSetter: StateUpdater<PageStateType>, +// i18n: InternationalizationAPI, +// ): Promise<void> { +// if (backendState?.status === "loggedOut") { +// logger.error("Page has a problem: no credentials found in the state."); +// pageStateSetter((prevState) => ({ +// ...prevState, + +// error: { +// title: i18n.str`No credentials given.`, +// }, +// })); +// return; +// } + +// let res: Response; +// try { +// const { username, password } = backendState; +// const headers = prepareHeaders(username, password); + +// // Let bank generate withdraw URI: +// const url = new URL( +// `access-api/accounts/${backendState.username}/withdrawals`, +// backendState.url, +// ); +// res = await fetch(url.href, { +// method: "POST", +// headers, +// body: JSON.stringify({ amount }), +// }); +// } catch (error) { +// logger.trace("Could not POST withdrawal request to the bank", error); +// pageStateSetter((prevState) => ({ +// ...prevState, + +// error: { +// title: i18n.str`Could not create withdrawal operation`, +// description: (error as any).error.description, +// debug: JSON.stringify(error), +// }, +// })); +// return; +// } +// if (!res.ok) { +// const response = await res.json(); +// logger.error( +// `Withdrawal creation gave response error: ${response} (${res.status})`, +// ); +// pageStateSetter((prevState) => ({ +// ...prevState, + +// error: { +// title: i18n.str`Withdrawal creation gave response error`, +// description: response.error.description, +// debug: JSON.stringify(response), +// }, +// })); +// return; +// } + +// logger.trace("Withdrawal operation created!"); +// const resp = await res.json(); +// pageStateSetter((prevState: PageStateType) => ({ +// ...prevState, +// withdrawalInProgress: true, +// talerWithdrawUri: resp.taler_withdraw_uri, +// withdrawalId: resp.withdrawal_id, +// })); +// } |