diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/WalletWithdrawForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/WalletWithdrawForm.tsx | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx new file mode 100644 index 000000000..ed2898952 --- /dev/null +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -0,0 +1,187 @@ +/* + 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 { 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 { + InternationalizationAPI, + useTranslationContext, +} from "@gnu-taler/web-util/lib/index.browser"; +import { BackendState } from "../hooks/backend.js"; +import { prepareHeaders, validateAmount } from "../utils.js"; + +const logger = new Logger("WalletWithdrawForm"); + +export function WalletWithdrawForm({ + focus, + currency, +}: { + currency?: string; + focus?: boolean; +}): VNode { + const backend = useBackendContext(); + const { pageState, pageStateSetter } = usePageContext(); + const { i18n } = useTranslationContext(); + let submitAmount: string | undefined = "5.00"; + + const ref = useRef<HTMLInputElement>(null); + useEffect(() => { + if (focus) ref.current?.focus(); + }, [focus]); + return ( + <form id="reserve-form" class="pure-form" name="tform"> + <p> + <label for="withdraw-amount">{i18n.str`Amount to withdraw:`}</label> + + <div style={{ width: "max-content" }}> + <input + type="text" + readonly + class="currency-indicator" + size={currency?.length ?? 5} + maxLength={currency?.length} + tabIndex={-1} + value={currency} + /> + + <input + type="number" + ref={ref} + id="withdraw-amount" + name="withdraw-amount" + value={submitAmount} + onChange={(e): void => { + // FIXME: validate using 'parseAmount()', + // deactivate submit button as long as + // amount is not valid + submitAmount = e.currentTarget.value; + }} + /> + </div> + </p> + <p> + <div> + <input + id="select-exchange" + class="pure-button pure-button-primary" + type="submit" + value={i18n.str`Withdraw`} + onClick={() => { + 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, + ); + }} + /> + </div> + </p> + </form> + ); +} + +/** + * 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, + })); +} |