diff options
Diffstat (limited to 'packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx')
-rw-r--r-- | packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx | 317 |
1 files changed, 102 insertions, 215 deletions
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 <http://www.gnu.org/licenses/> */ -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<string | undefined>(undefined); + const [subject, setSubject] = useState<string | undefined>(undefined); + const [amount, setAmount] = useState<string | undefined>(undefined); const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>( undefined, ); const { i18n } = useTranslationContext(); const ibanRegex = "^[A-Z][A-Z][0-9]+$"; - let transactionData: TransactionRequestType; const ref = useRef<HTMLInputElement>(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 ( <div> <form @@ -90,21 +107,18 @@ export function PaytoWireTransferForm({ type="text" id="iban" name="iban" - value={submitData?.iban ?? ""} + value={iban ?? ""} placeholder="CC0123456789" required pattern={ibanRegex} onInput={(e): void => { - submitDataSetter((submitData) => ({ - ...submitData, - iban: e.currentTarget.value, - })); + setIban(e.currentTarget.value); }} /> <br /> <ShowInputErrorLabel message={errorsWire?.iban} - isDirty={submitData?.iban !== undefined} + isDirty={iban !== undefined} /> <br /> <label for="subject">{i18n.str`Transfer subject:`}</label> @@ -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); }} /> <br /> <ShowInputErrorLabel message={errorsWire?.subject} - isDirty={submitData?.subject !== undefined} + isDirty={subject !== undefined} /> <br /> <label for="amount">{i18n.str`Amount:`}</label> @@ -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); }} /> </div> <ShowInputErrorLabel message={errorsWire?.amount} - isDirty={submitData?.amount !== undefined} + isDirty={amount !== undefined} /> </p> @@ -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, + // ); }} /> <input @@ -214,11 +207,9 @@ export function PaytoWireTransferForm({ value="Clear" onClick={async (e) => { e.preventDefault(); - submitDataSetter((p) => ({ - amount: undefined, - iban: undefined, - subject: undefined, - })); + setAmount(undefined); + setIban(undefined); + setSubject(undefined); }} /> </p> @@ -227,11 +218,7 @@ export function PaytoWireTransferForm({ <a href="/account" onClick={() => { - 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({ </div> ); + 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), + }); + } + } }} /> </p> @@ -322,11 +325,7 @@ export function PaytoWireTransferForm({ <a href="/account" onClick={() => { - 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({ </div> ); } - -/** - * 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 <input> fields. FIXME: name not matching the - * purpose, as this is not a HTTP request body but rather the - * state of the <input>-elements. - */ -type WireTransferRequestTypeOpt = WireTransferRequestType | undefined; -function useWireTransferRequestType( - state?: WireTransferRequestType, -): [WireTransferRequestTypeOpt, StateUpdater<WireTransferRequestTypeOpt>] { - const ret = useLocalStorage( - "wire-transfer-request-state", - JSON.stringify(state), - ); - const retObj: WireTransferRequestTypeOpt = ret[0] - ? JSON.parse(ret[0]) - : ret[0]; - const retSetter: StateUpdater<WireTransferRequestTypeOpt> = 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<PageStateType>, - /** - * Optional since the raw payto form doesn't have - * a stateful management of the input data yet. - */ - cleanUpForm: () => void, - i18n: InternationalizationAPI, -): Promise<void> { - 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(); -} |