From a8c5a9696c1735a178158cbc9ac4f9bb4b6f013d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 8 Feb 2023 17:41:19 -0300 Subject: impl accout management and refactor --- .../src/pages/PaytoWireTransferForm.tsx | 317 +++++++-------------- 1 file changed, 102 insertions(+), 215 deletions(-) (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 index 46b006880..d859b1cc7 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -14,64 +14,81 @@ 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 { + Amounts, + buildPayto, + Logger, + parsePaytoUri, + stringifyPaytoUri, +} from "@gnu-taler/taler-util"; import { InternationalizationAPI, + RequestError, useTranslationContext, } 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 { useAccessAPI } from "../hooks/access.js"; import { BackendState } from "../hooks/backend.js"; -import { prepareHeaders, undefinedIfEmpty } from "../utils.js"; +import { undefinedIfEmpty } from "../utils.js"; import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; const logger = new Logger("PaytoWireTransferForm"); export function PaytoWireTransferForm({ focus, + onError, + onSuccess, currency, }: { focus?: boolean; - currency?: string; + onError: (e: PageStateType["error"]) => void; + onSuccess: () => void; + currency: string; }): VNode { const backend = useBackendContext(); - const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button? + // const { pageState, pageStateSetter } = usePageContext(); // NOTE: used for go-back button? - const [submitData, submitDataSetter] = useWireTransferRequestType(); + const [isRawPayto, setIsRawPayto] = useState(false); + // const [submitData, submitDataSetter] = useWireTransferRequestType(); + const [iban, setIban] = useState(undefined); + const [subject, setSubject] = useState(undefined); + const [amount, setAmount] = useState(undefined); 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]); + }, [focus, isRawPayto]); let parsedAmount = undefined; + const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/; const errorsWire = undefinedIfEmpty({ - iban: !submitData?.iban + iban: !iban ? i18n.str`Missing IBAN` - : !/^[A-Z0-9]*$/.test(submitData.iban) + : !IBAN_REGEX.test(iban) ? i18n.str`IBAN should have just uppercased letters and numbers` : undefined, - subject: !submitData?.subject ? i18n.str`Missing subject` : undefined, - amount: !submitData?.amount + subject: !subject ? i18n.str`Missing subject` : undefined, + amount: !amount ? i18n.str`Missing amount` - : !(parsedAmount = Amounts.parse(`${currency}:${submitData.amount}`)) + : !(parsedAmount = Amounts.parse(`${currency}:${amount}`)) ? i18n.str`Amount is not valid` : Amounts.isZero(parsedAmount) ? i18n.str`Should be greater than 0` : undefined, }); - if (!pageState.isRawPayto) + const { createTransaction } = useAccessAPI(); + + if (!isRawPayto) return (
{ - submitDataSetter((submitData) => ({ - ...submitData, - iban: e.currentTarget.value, - })); + setIban(e.currentTarget.value); }} />

  @@ -113,19 +127,16 @@ export function PaytoWireTransferForm({ name="subject" id="subject" placeholder="subject" - value={submitData?.subject ?? ""} + value={subject ?? ""} required onInput={(e): void => { - submitDataSetter((submitData) => ({ - ...submitData, - subject: e.currentTarget.value, - })); + setSubject(e.currentTarget.value); }} />

  @@ -146,18 +157,15 @@ export function PaytoWireTransferForm({ id="amount" placeholder="amount" required - value={submitData?.amount ?? ""} + value={amount ?? ""} onInput={(e): void => { - submitDataSetter((submitData) => ({ - ...submitData, - amount: e.currentTarget.value, - })); + setAmount(e.currentTarget.value); }} />

@@ -169,43 +177,28 @@ export function PaytoWireTransferForm({ value="Send" onClick={async (e) => { e.preventDefault(); - 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.`, - }, - })); + if (!(iban && subject && amount)) { 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, - ); + const ibanPayto = buildPayto("iban", iban, undefined); + ibanPayto.params.message = encodeURIComponent(subject); + const paytoUri = stringifyPaytoUri(ibanPayto); + + await createTransaction({ + paytoUri, + amount: `${currency}:${amount}`, + }); + // return await createTransactionCall( + // transactionData, + // backend.state, + // pageStateSetter, + // () => { + // setAmount(undefined); + // setIban(undefined); + // setSubject(undefined); + // }, + // i18n, + // ); }} /> { e.preventDefault(); - submitDataSetter((p) => ({ - amount: undefined, - iban: undefined, - subject: undefined, - })); + setAmount(undefined); + setIban(undefined); + setSubject(undefined); }} />

@@ -227,11 +218,7 @@ export function PaytoWireTransferForm({ { - logger.trace("switch to raw payto form"); - pageStateSetter((prevState) => ({ - ...prevState, - isRawPayto: true, - })); + setIsRawPayto(true); }} > {i18n.str`Want to try the raw payto://-format?`} @@ -240,11 +227,23 @@ export function PaytoWireTransferForm({ ); + const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput); + const errorsPayto = undefinedIfEmpty({ rawPaytoInput: !rawPaytoInput - ? i18n.str`Missing payto address` - : !parsePaytoUri(rawPaytoInput) - ? i18n.str`Payto does not follow the pattern` + ? i18n.str`required` + : !parsed + ? i18n.str`does not follow the pattern` + : !parsed.params.amount + ? i18n.str`use the "amount" parameter to specify the amount to be transferred` + : Amounts.parse(parsed.params.amount) === undefined + ? i18n.str`the amount is not valid` + : !parsed.params.message + ? i18n.str`use the "message" parameter to specify a reference text for the transfer` + : !parsed.isKnown || parsed.targetType !== "iban" + ? i18n.str`only "IBAN" target are supported` + : !IBAN_REGEX.test(parsed.iban) + ? i18n.str`IBAN should have just uppercased letters and numbers` : undefined, }); @@ -296,25 +295,29 @@ export function PaytoWireTransferForm({ disabled={!!errorsPayto} value={i18n.str`Send`} onClick={async () => { - // 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, - ); + try { + await createTransaction({ + paytoUri: rawPaytoInput, + }); + onSuccess(); + rawPaytoInputSetter(undefined); + } catch (error) { + if (error instanceof RequestError) { + const errorData: SandboxBackend.SandboxError = + error.info.error; + + onError({ + title: i18n.str`Transfer creation gave response error`, + description: errorData.error.description, + debug: JSON.stringify(errorData), + }); + } + } }} />

@@ -322,11 +325,7 @@ export function PaytoWireTransferForm({
{ - logger.trace("switch to wire-transfer-form"); - pageStateSetter((prevState) => ({ - ...prevState, - isRawPayto: false, - })); + setIsRawPayto(false); }} > {i18n.str`Use wire-transfer form?`} @@ -336,115 +335,3 @@ export function PaytoWireTransferForm({ ); } - -/** - * 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