From 5fc8f95a5d4ce8dea03b2dbec7eb5c37e7ff3f15 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 9 Dec 2022 12:15:15 -0300 Subject: simplify directories --- .../src/pages/PaytoWireTransferForm.tsx | 432 +++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx (limited to 'packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx') diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx new file mode 100644 index 000000000..1237f5eb1 --- /dev/null +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -0,0 +1,432 @@ +/* + 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 { Amounts, Logger, parsePaytoUri } from "@gnu-taler/taler-util"; +import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser"; +import { h, VNode } from "preact"; +import { StateUpdater, useEffect, useRef, useState } 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, undefinedIfEmpty } from "../utils.js"; +import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; + +const logger = new Logger("PaytoWireTransferForm"); + +export function PaytoWireTransferForm({ + focus, + currency, +}: { + focus?: boolean; + currency?: string; +}): VNode { + const backend = useBackendContext(); + const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button? + + const [submitData, submitDataSetter] = useWireTransferRequestType(); + + const [rawPaytoInput, rawPaytoInputSetter] = useState( + undefined, + ); + const { i18n } = useTranslationContext(); + const ibanRegex = "^[A-Z][A-Z][0-9]+$"; + let transactionData: TransactionRequestType; + const ref = useRef(null); + useEffect(() => { + if (focus) ref.current?.focus(); + }, [focus, pageState.isRawPayto]); + + let parsedAmount = undefined; + + const errorsWire = { + iban: !submitData?.iban + ? i18n.str`Missing IBAN` + : !/^[A-Z0-9]*$/.test(submitData.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` + : undefined, + subject: !submitData?.subject ? i18n.str`Missing subject` : undefined, + amount: !submitData?.amount + ? i18n.str`Missing amount` + : !(parsedAmount = Amounts.parse(`${currency}:${submitData.amount}`)) + ? i18n.str`Amount is not valid` + : Amounts.isZero(parsedAmount) + ? i18n.str`Should be greater than 0` + : undefined, + }; + + if (!pageState.isRawPayto) + return ( +
+
+

+   + { + submitDataSetter((submitData) => ({ + ...submitData, + iban: e.currentTarget.value, + })); + }} + /> +
+ +
+   + { + submitDataSetter((submitData) => ({ + ...submitData, + subject: e.currentTarget.value, + })); + }} + /> +
+ +
+   +

+ +   + { + submitDataSetter((submitData) => ({ + ...submitData, + amount: e.currentTarget.value, + })); + }} + /> +
+ +

+ +

+ { + if ( + typeof submitData === "undefined" || + typeof submitData.iban === "undefined" || + submitData.iban === "" || + typeof submitData.subject === "undefined" || + submitData.subject === "" || + typeof submitData.amount === "undefined" || + submitData.amount === "" + ) { + logger.error("Not all the fields were given."); + pageStateSetter((prevState: PageStateType) => ({ + ...prevState, + + error: { + title: i18n.str`Field(s) missing.`, + }, + })); + return; + } + transactionData = { + paytoUri: `payto://iban/${ + submitData.iban + }?message=${encodeURIComponent(submitData.subject)}`, + amount: `${currency}:${submitData.amount}`, + }; + return await createTransactionCall( + transactionData, + backend.state, + pageStateSetter, + () => + submitDataSetter((p) => ({ + amount: undefined, + iban: undefined, + subject: undefined, + })), + i18n, + ); + }} + /> + { + submitDataSetter((p) => ({ + amount: undefined, + iban: undefined, + subject: undefined, + })); + }} + /> +

+ +

+ { + logger.trace("switch to raw payto form"); + pageStateSetter((prevState) => ({ + ...prevState, + isRawPayto: true, + })); + }} + > + {i18n.str`Want to try the raw payto://-format?`} + +

+
+ ); + + const errorsPayto = undefinedIfEmpty({ + rawPaytoInput: !rawPaytoInput + ? i18n.str`Missing payto address` + : !parsePaytoUri(rawPaytoInput) + ? i18n.str`Payto does not follow the pattern` + : undefined, + }); + + return ( +
+

{i18n.str`Transfer money to account identified by payto:// URI:`}

+
+

+   + { + rawPaytoInputSetter(e.currentTarget.value); + }} + /> + +
+

+ Hint: + + payto://iban/[receiver-iban]?message=[subject]&amount=[{currency} + :X.Y] + +
+

+

+ { + // empty string evaluates to false. + if (!rawPaytoInput) { + logger.error("Didn't get any raw Payto string!"); + return; + } + transactionData = { paytoUri: rawPaytoInput }; + if ( + typeof transactionData.paytoUri === "undefined" || + transactionData.paytoUri.length === 0 + ) + return; + + return await createTransactionCall( + transactionData, + backend.state, + pageStateSetter, + () => rawPaytoInputSetter(undefined), + i18n, + ); + }} + /> +

+

+ { + logger.trace("switch to wire-transfer-form"); + pageStateSetter((prevState) => ({ + ...prevState, + isRawPayto: false, + })); + }} + > + {i18n.str`Use wire-transfer form?`} + +

+
+
+ ); +} + +/** + * Stores in the state a object representing a wire transfer, + * in order to avoid losing the handle of the data entered by + * the user in fields. FIXME: name not matching the + * purpose, as this is not a HTTP request body but rather the + * state of the -elements. + */ +type WireTransferRequestTypeOpt = WireTransferRequestType | undefined; +function useWireTransferRequestType( + state?: WireTransferRequestType, +): [WireTransferRequestTypeOpt, StateUpdater] { + const ret = useLocalStorage( + "wire-transfer-request-state", + JSON.stringify(state), + ); + const retObj: WireTransferRequestTypeOpt = ret[0] + ? JSON.parse(ret[0]) + : ret[0]; + const retSetter: StateUpdater = function (val) { + const newVal = + val instanceof Function + ? JSON.stringify(val(retObj)) + : JSON.stringify(val); + ret[1](newVal); + }; + return [retObj, retSetter]; +} + +/** + * This function creates a new transaction. It reads a Payto + * address entered by the user and POSTs it to the bank. No + * sanity-check of the input happens before the POST as this is + * already conducted by the backend. + */ +async function createTransactionCall( + req: TransactionRequestType, + backendState: BackendState, + pageStateSetter: StateUpdater, + /** + * Optional since the raw payto form doesn't have + * a stateful management of the input data yet. + */ + cleanUpForm: () => void, + i18n: InternationalizationAPI, +): Promise { + if (backendState.status === "loggedOut") { + logger.error("No credentials found."); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`No credentials found.`, + }, + })); + return; + } + let res: Response; + try { + const { username, password } = backendState; + const headers = prepareHeaders(username, password); + const url = new URL( + `access-api/accounts/${backendState.username}/transactions`, + backendState.url, + ); + res = await fetch(url.href, { + method: "POST", + headers, + body: JSON.stringify(req), + }); + } catch (error) { + logger.error("Could not POST transaction request to the bank", error); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`Could not create the wire transfer`, + description: (error as any).error.description, + debug: JSON.stringify(error), + }, + })); + return; + } + // POST happened, status not sure yet. + if (!res.ok) { + const response = await res.json(); + logger.error( + `Transfer creation gave response error: ${response} (${res.status})`, + ); + pageStateSetter((prevState) => ({ + ...prevState, + + error: { + title: i18n.str`Transfer creation gave response error`, + description: response.error.description, + debug: JSON.stringify(response), + }, + })); + return; + } + // status is 200 OK here, tell the user. + logger.trace("Wire transfer created!"); + pageStateSetter((prevState) => ({ + ...prevState, + + info: i18n.str`Wire transfer created!`, + })); + + // Only at this point the input data can + // be discarded. + cleanUpForm(); +} -- cgit v1.2.3