summaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx')
-rw-r--r--packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx317
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>&nbsp;
@@ -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>&nbsp;
@@ -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();
-}