+
+ );
+}
diff --git a/packages/demobank-ui/src/components/menu/LangSelector.tsx b/packages/demobank-ui/src/components/menu/LangSelector.tsx
deleted file mode 100644
index 6c0acaf42..000000000
--- a/packages/demobank-ui/src/components/menu/LangSelector.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- 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
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { Fragment, h, VNode } from "preact";
-import { useEffect, useState } from "preact/hooks";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { strings as messages } from "../../i18n/strings.js";
-
-type LangsNames = {
- [P in keyof typeof messages]: string;
-};
-
-const names: LangsNames = {
- es: "Español [es]",
- en: "English [en]",
- fr: "Français [fr]",
- de: "Deutsch [de]",
- sv: "Svenska [sv]",
- it: "Italiano [it]",
-};
-
-function getLangName(s: keyof LangsNames | string): string {
- if (names[s]) return names[s];
- return String(s);
-}
-
-// FIXME: explain "like py".
-export function LangSelectorLikePy(): VNode {
- const [updatingLang, setUpdatingLang] = useState(false);
- const { lang, changeLanguage } = useTranslationContext();
- const [hidden, setHidden] = useState(true);
- useEffect(() => {
- function bodyKeyPress(event: KeyboardEvent) {
- if (event.code === "Escape") setHidden(true);
- }
- function bodyOnClick(event: Event) {
- setHidden(true);
- }
- document.body.addEventListener("click", bodyOnClick);
- document.body.addEventListener("keydown", bodyKeyPress as any);
- return () => {
- document.body.removeEventListener("keydown", bodyKeyPress as any);
- document.body.removeEventListener("click", bodyOnClick);
- };
- }, []);
- return (
-
-
-
-
- );
-}
diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx b/packages/demobank-ui/src/pages/AccountPage.tsx
new file mode 100644
index 000000000..7ec4d36fb
--- /dev/null
+++ b/packages/demobank-ui/src/pages/AccountPage.tsx
@@ -0,0 +1,266 @@
+/*
+ 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, HttpStatusCode, Logger } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { useEffect } from "preact/hooks";
+import useSWR, { SWRConfig, useSWRConfig } from "swr";
+import { useBackendContext } from "../context/backend.js";
+import { PageStateType, usePageContext } from "../context/pageState.js";
+import { BackendInfo } from "../hooks/backend.js";
+import { bankUiSettings } from "../settings.js";
+import { getIbanFromPayto, prepareHeaders } from "../utils.js";
+import { BankFrame } from "./BankFrame.js";
+import { LoginForm } from "./LoginForm.js";
+import { PaymentOptions } from "./PaymentOptions.js";
+import { Transactions } from "./Transactions.js";
+import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
+
+export function AccountPage(): VNode {
+ const backend = useBackendContext();
+ const { i18n } = useTranslationContext();
+
+ if (backend.state.status === "loggedOut") {
+ return (
+
+
{i18n.str`Welcome to ${bankUiSettings.bankName}!`}
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
+
+/**
+ * Factor out login credentials.
+ */
+function SWRWithCredentials({
+ children,
+ info,
+}: {
+ children: ComponentChildren;
+ info: BackendInfo;
+}): VNode {
+ const { username, password, url: backendUrl } = info;
+ const headers = prepareHeaders(username, password);
+ return (
+ {
+ return fetch(new URL(url, backendUrl).href, { headers }).then((r) => {
+ if (!r.ok) throw { status: r.status, json: r.json() };
+
+ return r.json();
+ });
+ },
+ }}
+ >
+ {children as any}
+
+ );
+}
+
+const logger = new Logger("AccountPage");
+
+/**
+ * Show only the account's balance. NOTE: the backend state
+ * is mostly needed to provide the user's credentials to POST
+ * to the bank.
+ */
+function Account({ accountLabel }: { accountLabel: string }): VNode {
+ const { cache } = useSWRConfig();
+
+ // Getting the bank account balance:
+ const endpoint = `access-api/accounts/${accountLabel}`;
+ const { data, error, mutate } = useSWR(endpoint, {
+ // refreshInterval: 0,
+ // revalidateIfStale: false,
+ // revalidateOnMount: false,
+ // revalidateOnFocus: false,
+ // revalidateOnReconnect: false,
+ });
+ const backend = useBackendContext();
+ const { pageState, pageStateSetter: setPageState } = usePageContext();
+ const { withdrawalId, talerWithdrawUri, timestamp } = pageState;
+ const { i18n } = useTranslationContext();
+ useEffect(() => {
+ mutate();
+ }, [timestamp]);
+
+ /**
+ * This part shows a list of transactions: with 5 elements by
+ * default and offers a "load more" button.
+ */
+ // const [txPageNumber, setTxPageNumber] = useTransactionPageNumber();
+ // const txsPages = [];
+ // for (let i = 0; i <= txPageNumber; i++) {
+ // txsPages.push();
+ // }
+
+ if (typeof error !== "undefined") {
+ logger.error("account error", error, endpoint);
+ /**
+ * FIXME: to minimize the code, try only one invocation
+ * of pageStateSetter, after having decided the error
+ * message in the case-branch.
+ */
+ switch (error.status) {
+ case 404: {
+ backend.clear();
+ setPageState((prevState: PageStateType) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`Username or account label '${accountLabel}' not found. Won't login.`,
+ },
+ }));
+
+ /**
+ * 404 should never stick to the cache, because they
+ * taint successful future registrations. How? After
+ * registering, the user gets navigated to this page,
+ * therefore a previous 404 on this SWR key (the requested
+ * resource) would still appear as valid and cause this
+ * page not to be shown! A typical case is an attempted
+ * login of a unregistered user X, and then a registration
+ * attempt of the same user X: in this case, the failed
+ * login would cache a 404 error to X's profile, resulting
+ * in the legitimate request after the registration to still
+ * be flagged as 404. Clearing the cache should prevent
+ * this. */
+ (cache as any).clear();
+ return
;
+ }
+ default: {
+ backend.clear();
+ setPageState((prevState: PageStateType) => ({
+ ...prevState,
+ error: {
+ title: i18n.str`Account information could not be retrieved.`,
+ debug: JSON.stringify(error),
+ },
+ }));
+ return
Unknown problem...
;
+ }
+ }
+ }
+ const balance = !data ? undefined : Amounts.parseOrThrow(data.balance.amount);
+ const accountNumber = !data ? undefined : getIbanFromPayto(data.paytoUri);
+ const balanceIsDebit = data && data.balance.credit_debit_indicator == "debit";
+
+ /**
+ * This block shows the withdrawal QR code.
+ *
+ * A withdrawal operation replaces everything in the page and
+ * (ToDo:) starts polling the backend until either the wallet
+ * selected a exchange and reserve public key, or a error / abort
+ * happened.
+ *
+ * After reaching one of the above states, the user should be
+ * brought to this ("Account") page where they get informed about
+ * the outcome.
+ */
+ if (talerWithdrawUri && withdrawalId) {
+ logger.trace("Bank created a new Taler withdrawal");
+ return (
+
+
+
+ );
+ }
+ const balanceValue = !balance ? undefined : Amounts.stringifyValue(balance);
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+// function useTransactionPageNumber(): [number, StateUpdater] {
+// const ret = useNotNullLocalStorage("transaction-page", "0");
+// const retObj = JSON.parse(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];
+// }
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx
new file mode 100644
index 000000000..e36629e2a
--- /dev/null
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -0,0 +1,192 @@
+/*
+ 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 { Logger } from "@gnu-taler/taler-util";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import talerLogo from "../assets/logo-white.svg";
+import { LangSelectorLikePy as LangSelector } from "../components/LangSelector.js";
+import { useBackendContext } from "../context/backend.js";
+import { PageStateType, usePageContext } from "../context/pageState.js";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { bankUiSettings } from "../settings.js";
+
+const logger = new Logger("BankFrame");
+
+export function BankFrame({
+ children,
+}: {
+ children: ComponentChildren;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const backend = useBackendContext();
+ const { pageState, pageStateSetter } = usePageContext();
+ logger.trace("state", pageState);
+ const logOut = (
+
+
+ This part of the demo shows how a bank that supports Taler
+ directly would work. In addition to using your own bank account,
+ you can also see the transaction history of some{" "}
+ Public Accounts.
+
+
+ );
+ return rval;
+}
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx
new file mode 100644
index 000000000..61d3c1e49
--- /dev/null
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -0,0 +1,139 @@
+/*
+ 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 { h, VNode } from "preact";
+import { route } from "preact-router";
+import { useEffect, useRef, useState } from "preact/hooks";
+import { useBackendContext } from "../context/backend.js";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { BackendStateHandler } from "../hooks/backend.js";
+import { bankUiSettings } from "../settings.js";
+import { getBankBackendBaseUrl, undefinedIfEmpty } from "../utils.js";
+import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+
+/**
+ * Collect and submit login data.
+ */
+export function LoginForm(): VNode {
+ const backend = useBackendContext();
+ const [username, setUsername] = useState();
+ const [password, setPassword] = useState();
+ const { i18n } = useTranslationContext();
+ const ref = useRef(null);
+ useEffect(() => {
+ ref.current?.focus();
+ }, []);
+
+ const errors = undefinedIfEmpty({
+ username: !username ? i18n.str`Missing username` : undefined,
+ password: !password ? i18n.str`Missing password` : undefined,
+ });
+
+ return (
+
+
+
+ );
+}
+
+async function loginCall(
+ req: { username: string; password: string },
+ /**
+ * FIXME: figure out if the two following
+ * functions can be retrieved from the state.
+ */
+ backend: BackendStateHandler,
+): Promise {
+ /**
+ * Optimistically setting the state as 'logged in', and
+ * let the Account component request the balance to check
+ * whether the credentials are valid. */
+
+ backend.save({
+ url: getBankBackendBaseUrl(),
+ username: req.username,
+ password: req.password,
+ });
+}
diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx
new file mode 100644
index 000000000..ae876d556
--- /dev/null
+++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx
@@ -0,0 +1,70 @@
+/*
+ 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 { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
+import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
+
+/**
+ * Let the user choose a payment option,
+ * then specify the details trigger the action.
+ */
+export function PaymentOptions({ currency }: { currency?: string }): VNode {
+ const { i18n } = useTranslationContext();
+
+ const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">(
+ "charge-wallet",
+ );
+
+ return (
+
+
+
+
+
+
+ {tab === "charge-wallet" && (
+
+
{i18n.str`Obtain digital cash`}
+
+
+ )}
+ {tab === "wire-transfer" && (
+
+
{i18n.str`Transfer to bank account`}
+
+
+ )}
+
+
+ );
+}
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 (
+
+ );
+}
+
+/**
+ * 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();
+}
diff --git a/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
new file mode 100644
index 000000000..be9f4aee1
--- /dev/null
+++ b/packages/demobank-ui/src/pages/PublicHistoriesPage.tsx
@@ -0,0 +1,182 @@
+/*
+ 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 { Logger } from "@gnu-taler/taler-util";
+import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
+import { ComponentChildren, Fragment, h, VNode } from "preact";
+import { route } from "preact-router";
+import { StateUpdater } from "preact/hooks";
+import useSWR, { SWRConfig } from "swr";
+import { PageStateType, usePageContext } from "../context/pageState.js";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { getBankBackendBaseUrl } from "../utils.js";
+import { BankFrame } from "./BankFrame.js";
+import { Transactions } from "./Transactions.js";
+
+const logger = new Logger("PublicHistoriesPage");
+
+export function PublicHistoriesPage(): VNode {
+ return (
+
+
+
+
+
+ );
+}
+
+function SWRWithoutCredentials({
+ baseUrl,
+ children,
+}: {
+ children: ComponentChildren;
+ baseUrl: string;
+}): VNode {
+ logger.trace("Base URL", baseUrl);
+ return (
+
+ fetch(baseUrl + url || "").then((r) => {
+ if (!r.ok) throw { status: r.status, json: r.json() };
+
+ return r.json();
+ }),
+ }}
+ >
+ {children as any}
+
+ );
+}
+
+/**
+ * Show histories of public accounts.
+ */
+function PublicHistories(): VNode {
+ const { pageState, pageStateSetter } = usePageContext();
+ const [showAccount, setShowAccount] = useShowPublicAccount();
+ const { data, error } = useSWR("access-api/public-accounts");
+ const { i18n } = useTranslationContext();
+
+ if (typeof error !== "undefined") {
+ switch (error.status) {
+ case 404:
+ logger.error("public accounts: 404", error);
+ route("/account");
+ pageStateSetter((prevState: PageStateType) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`List of public accounts was not found.`,
+ debug: JSON.stringify(error),
+ },
+ }));
+ break;
+ default:
+ logger.error("public accounts: non-404 error", error);
+ route("/account");
+ pageStateSetter((prevState: PageStateType) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`List of public accounts could not be retrieved.`,
+ debug: JSON.stringify(error),
+ },
+ }));
+ break;
+ }
+ }
+ if (!data) return
Waiting public accounts list...
;
+ const txs: Record = {};
+ const accountsBar = [];
+
+ /**
+ * Show the account specified in the props, or just one
+ * from the list if that's not given.
+ */
+ if (typeof showAccount === "undefined" && data.publicAccounts.length > 0) {
+ setShowAccount(data.publicAccounts[1].accountLabel);
+ }
+ logger.trace(`Public history tab: ${showAccount}`);
+
+ // Ask story of all the public accounts.
+ for (const account of data.publicAccounts) {
+ logger.trace("Asking transactions for", account.accountLabel);
+ const isSelected = account.accountLabel == showAccount;
+ accountsBar.push(
+
+
+
+
+ );
+}
+
+/**
+ * Stores in the state a object containing a 'username'
+ * and 'password' field, in order to avoid losing the
+ * handle of the data entered by the user in fields.
+ */
+function useShowPublicAccount(
+ state?: string,
+): [string | undefined, StateUpdater] {
+ const ret = useLocalStorage("show-public-account", JSON.stringify(state));
+ const retObj: string | undefined = 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];
+}
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx b/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
new file mode 100644
index 000000000..521d4255e
--- /dev/null
+++ b/packages/demobank-ui/src/pages/QrCodeSection.stories.tsx
@@ -0,0 +1,33 @@
+/*
+ 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
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { QrCodeSection } from "./QrCodeSection.js";
+
+export default {
+ title: "Qr Code Selection",
+};
+
+export const SimpleExample = {
+ component: QrCodeSection,
+ props: {
+ talerWithdrawUri: "taler://withdraw/asdasdasd",
+ },
+};
diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx
new file mode 100644
index 000000000..59a8ccd61
--- /dev/null
+++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx
@@ -0,0 +1,59 @@
+/*
+ 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 { h, VNode } from "preact";
+import { useEffect } from "preact/hooks";
+import { QR } from "../components/QR.js";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+
+export function QrCodeSection({
+ talerWithdrawUri,
+ abortButton,
+}: {
+ talerWithdrawUri: string;
+ abortButton: h.JSX.Element;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ useEffect(() => {
+ //Taler Wallet WebExtension is listening to headers response and tab updates.
+ //In the SPA there is no header response with the Taler URI so
+ //this hack manually triggers the tab update after the QR is in the DOM.
+ // WebExtension will be using
+ // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
+ document.title = `${document.title} ${talerWithdrawUri}`;
+ }, []);
+
+ return (
+
+
{i18n.str`Transfer to Taler Wallet`}
+
+
+
{i18n.str`Use this QR code to withdraw to your mobile wallet:`}
+
+
+ );
+}
diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx
new file mode 100644
index 000000000..c97cb56f3
--- /dev/null
+++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx
@@ -0,0 +1,262 @@
+/*
+ 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 { Logger } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { route } from "preact-router";
+import { StateUpdater, 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 { BackendStateHandler } from "../hooks/backend.js";
+import { bankUiSettings } from "../settings.js";
+import { getBankBackendBaseUrl, undefinedIfEmpty } from "../utils.js";
+import { BankFrame } from "./BankFrame.js";
+import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
+
+const logger = new Logger("RegistrationPage");
+
+export function RegistrationPage(): VNode {
+ const { i18n } = useTranslationContext();
+ if (!bankUiSettings.allowRegistrations) {
+ return (
+
+
{i18n.str`Currently, the bank is not accepting new registrations!`}
{i18n.str`Welcome to ${bankUiSettings.bankName}!`}
+
+
+
+
+
+
+ );
+}
+
+/**
+ * This function requests /register.
+ *
+ * This function is responsible to change two states:
+ * the backend's (to store the login credentials) and
+ * the page's (to indicate a successful login or a problem).
+ */
+async function registrationCall(
+ req: { username: string; password: string },
+ /**
+ * FIXME: figure out if the two following
+ * functions can be retrieved somewhat from
+ * the state.
+ */
+ backend: BackendStateHandler,
+ pageStateSetter: StateUpdater,
+ i18n: InternationalizationAPI,
+): Promise {
+ const url = getBankBackendBaseUrl();
+
+ const headers = new Headers();
+ headers.append("Content-Type", "application/json");
+ const registerEndpoint = new URL("access-api/testing/register", url);
+ let res: Response;
+ try {
+ res = await fetch(registerEndpoint.href, {
+ method: "POST",
+ body: JSON.stringify({
+ username: req.username,
+ password: req.password,
+ }),
+ headers,
+ });
+ } catch (error) {
+ logger.error(
+ `Could not POST new registration to the bank (${registerEndpoint.href})`,
+ error,
+ );
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`Registration failed, please report`,
+ debug: JSON.stringify(error),
+ },
+ }));
+ return;
+ }
+ if (!res.ok) {
+ const response = await res.json();
+ if (res.status === 409) {
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`That username is already taken`,
+ debug: JSON.stringify(response),
+ },
+ }));
+ } else {
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`New registration gave response error`,
+ debug: JSON.stringify(response),
+ },
+ }));
+ }
+ } else {
+ // registration was ok
+ backend.save({
+ url,
+ username: req.username,
+ password: req.password,
+ });
+ route("/account");
+ }
+}
diff --git a/packages/demobank-ui/src/pages/Routing.tsx b/packages/demobank-ui/src/pages/Routing.tsx
index 6b00df97b..3c3aae0ce 100644
--- a/packages/demobank-ui/src/pages/Routing.tsx
+++ b/packages/demobank-ui/src/pages/Routing.tsx
@@ -18,9 +18,9 @@ import { createHashHistory } from "history";
import { h, VNode } from "preact";
import Router, { route, Route } from "preact-router";
import { useEffect } from "preact/hooks";
-import { AccountPage } from "./home/AccountPage.js";
-import { PublicHistoriesPage } from "./home/PublicHistoriesPage.js";
-import { RegistrationPage } from "./home/RegistrationPage.js";
+import { AccountPage } from "./AccountPage.js";
+import { PublicHistoriesPage } from "./PublicHistoriesPage.js";
+import { RegistrationPage } from "./RegistrationPage.js";
export function Routing(): VNode {
const history = createHashHistory();
diff --git a/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx b/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx
new file mode 100644
index 000000000..dacffe20a
--- /dev/null
+++ b/packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx
@@ -0,0 +1,29 @@
+/*
+ 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 { Fragment, h, VNode } from "preact";
+
+export function ShowInputErrorLabel({
+ isDirty,
+ message,
+}: {
+ message: string | undefined;
+ isDirty: boolean;
+}): VNode {
+ if (message && isDirty)
+ return
{message}
;
+ return ;
+}
diff --git a/packages/demobank-ui/src/pages/Transactions.tsx b/packages/demobank-ui/src/pages/Transactions.tsx
new file mode 100644
index 000000000..ca88abd4d
--- /dev/null
+++ b/packages/demobank-ui/src/pages/Transactions.tsx
@@ -0,0 +1,106 @@
+/*
+ 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 { Logger } from "@gnu-taler/taler-util";
+import { h, VNode } from "preact";
+import { useEffect } from "preact/hooks";
+import useSWR from "swr";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+
+const logger = new Logger("Transactions");
+/**
+ * Show one page of transactions.
+ */
+export function Transactions({
+ pageNumber,
+ accountLabel,
+ balanceValue,
+}: {
+ pageNumber: number;
+ accountLabel: string;
+ balanceValue?: string;
+}): VNode {
+ const { i18n } = useTranslationContext();
+ const { data, error, mutate } = useSWR(
+ `access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
+ );
+ useEffect(() => {
+ if (balanceValue) {
+ mutate();
+ }
+ }, [balanceValue ?? ""]);
+ if (typeof error !== "undefined") {
+ logger.error("transactions not found error", error);
+ switch (error.status) {
+ case 404: {
+ return
Transactions page {pageNumber} was not found.
;
+ }
+ case 401: {
+ return
Wrong credentials given.
;
+ }
+ default: {
+ return
Transaction page {pageNumber} could not be retrieved.
;
+ }
+ }
+ }
+ if (!data) {
+ logger.trace(`History data of ${accountLabel} not arrived`);
+ return
Transactions page loading...
;
+ }
+ logger.trace(`History data of ${accountLabel}`, data);
+ return (
+
+ );
+}
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
+ */
+
+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(null);
+ useEffect(() => {
+ if (focus) ref.current?.focus();
+ }, [focus]);
+ return (
+
+
+
+ {
+ 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,
+ );
+ }}
+ />
+
+
+
+ );
+}
+
+/**
+ * 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,
+ i18n: InternationalizationAPI,
+): Promise {
+ 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,
+ }));
+}
diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
new file mode 100644
index 000000000..8cfdd4e9f
--- /dev/null
+++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx
@@ -0,0 +1,327 @@
+/*
+ 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 { Logger } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import { StateUpdater } 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 } from "../utils.js";
+
+const logger = new Logger("WithdrawalConfirmationQuestion");
+
+/**
+ * Additional authentication required to complete the operation.
+ * Not providing a back button, only abort.
+ */
+export function WithdrawalConfirmationQuestion(): VNode {
+ const { pageState, pageStateSetter } = usePageContext();
+ const backend = useBackendContext();
+ const { i18n } = useTranslationContext();
+ const captchaNumbers = {
+ a: Math.floor(Math.random() * 10),
+ b: Math.floor(Math.random() * 10),
+ };
+ let captchaAnswer = "";
+
+ return (
+
+
{i18n.str`Confirm Withdrawal`}
+
+
+
+
+
+
+ A this point, a real bank would ask for an additional
+ authentication proof (PIN/TAN, one time password, ..), instead
+ of a simple calculation.
+
+
+
+
+
+
+ );
+}
+
+/**
+ * This function confirms a withdrawal operation AFTER
+ * the wallet has given the exchange's payment details
+ * to the bank (via the Integration API). Such details
+ * can be given by scanning a QR code or by passing the
+ * raw taler://withdraw-URI to the CLI wallet.
+ *
+ * This function will set the confirmation status in the
+ * 'page state' and let the related components refresh.
+ */
+async function confirmWithdrawalCall(
+ backendState: BackendState,
+ withdrawalId: string | undefined,
+ pageStateSetter: StateUpdater,
+ i18n: InternationalizationAPI,
+): Promise {
+ if (backendState.status === "loggedOut") {
+ logger.error("No credentials found.");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`No credentials found.`,
+ },
+ }));
+ return;
+ }
+ if (typeof withdrawalId === "undefined") {
+ logger.error("No withdrawal ID found.");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`No withdrawal ID found.`,
+ },
+ }));
+ return;
+ }
+ let res: Response;
+ try {
+ const { username, password } = backendState;
+ const headers = prepareHeaders(username, password);
+ /**
+ * NOTE: tests show that when a same object is being
+ * POSTed, caching might prevent same requests from being
+ * made. Hence, trying to POST twice the same amount might
+ * get silently ignored.
+ *
+ * headers.append("cache-control", "no-store");
+ * headers.append("cache-control", "no-cache");
+ * headers.append("pragma", "no-cache");
+ * */
+
+ // Backend URL must have been stored _with_ a final slash.
+ const url = new URL(
+ `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/confirm`,
+ backendState.url,
+ );
+ res = await fetch(url.href, {
+ method: "POST",
+ headers,
+ });
+ } catch (error) {
+ logger.error("Could not POST withdrawal confirmation to the bank", error);
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`Could not confirm the withdrawal`,
+ description: (error as any).error.description,
+ debug: JSON.stringify(error),
+ },
+ }));
+ return;
+ }
+ if (!res || !res.ok) {
+ const response = await res.json();
+ // assume not ok if res is null
+ logger.error(
+ `Withdrawal confirmation gave response error (${res.status})`,
+ res.statusText,
+ );
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`Withdrawal confirmation gave response error`,
+ debug: JSON.stringify(response),
+ },
+ }));
+ return;
+ }
+ logger.trace("Withdrawal operation confirmed!");
+ pageStateSetter((prevState) => {
+ const { talerWithdrawUri, ...rest } = prevState;
+ return {
+ ...rest,
+
+ info: i18n.str`Withdrawal confirmed!`,
+ };
+ });
+}
+
+/**
+ * Abort a withdrawal operation via the Access API's /abort.
+ */
+async function abortWithdrawalCall(
+ backendState: BackendState,
+ withdrawalId: string | undefined,
+ pageStateSetter: StateUpdater,
+ i18n: InternationalizationAPI,
+): Promise {
+ if (backendState.status === "loggedOut") {
+ logger.error("No credentials found.");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`No credentials found.`,
+ },
+ }));
+ return;
+ }
+ if (typeof withdrawalId === "undefined") {
+ logger.error("No withdrawal ID found.");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`No withdrawal ID found.`,
+ },
+ }));
+ return;
+ }
+ let res: Response;
+ try {
+ const { username, password } = backendState;
+ const headers = prepareHeaders(username, password);
+ /**
+ * NOTE: tests show that when a same object is being
+ * POSTed, caching might prevent same requests from being
+ * made. Hence, trying to POST twice the same amount might
+ * get silently ignored. Needs more observation!
+ *
+ * headers.append("cache-control", "no-store");
+ * headers.append("cache-control", "no-cache");
+ * headers.append("pragma", "no-cache");
+ * */
+
+ // Backend URL must have been stored _with_ a final slash.
+ const url = new URL(
+ `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/abort`,
+ backendState.url,
+ );
+ res = await fetch(url.href, { method: "POST", headers });
+ } catch (error) {
+ logger.error("Could not abort the withdrawal", error);
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`Could not abort the withdrawal.`,
+ description: (error as any).error.description,
+ debug: JSON.stringify(error),
+ },
+ }));
+ return;
+ }
+ if (!res.ok) {
+ const response = await res.json();
+ logger.error(
+ `Withdrawal abort gave response error (${res.status})`,
+ res.statusText,
+ );
+ pageStateSetter((prevState) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`Withdrawal abortion failed.`,
+ description: response.error.description,
+ debug: JSON.stringify(response),
+ },
+ }));
+ return;
+ }
+ logger.trace("Withdrawal operation aborted!");
+ pageStateSetter((prevState) => {
+ const { ...rest } = prevState;
+ return {
+ ...rest,
+
+ info: i18n.str`Withdrawal aborted!`,
+ };
+ });
+}
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
new file mode 100644
index 000000000..174c19288
--- /dev/null
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -0,0 +1,120 @@
+/*
+ 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 { Logger } from "@gnu-taler/taler-util";
+import { Fragment, h, VNode } from "preact";
+import useSWR from "swr";
+import { PageStateType, usePageContext } from "../context/pageState.js";
+import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import { QrCodeSection } from "./QrCodeSection.js";
+import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
+
+const logger = new Logger("WithdrawalQRCode");
+/**
+ * Offer the QR code (and a clickable taler://-link) to
+ * permit the passing of exchange and reserve details to
+ * the bank. Poll the backend until such operation is done.
+ */
+export function WithdrawalQRCode({
+ withdrawalId,
+ talerWithdrawUri,
+}: {
+ withdrawalId: string;
+ talerWithdrawUri: string;
+}): VNode {
+ // turns true when the wallet POSTed the reserve details:
+ const { pageState, pageStateSetter } = usePageContext();
+ const { i18n } = useTranslationContext();
+ const abortButton = (
+ {
+ pageStateSetter((prevState: PageStateType) => {
+ return {
+ ...prevState,
+ withdrawalId: undefined,
+ talerWithdrawUri: undefined,
+ withdrawalInProgress: false,
+ };
+ });
+ }}
+ >{i18n.str`Abort`}
+ );
+
+ logger.trace(`Showing withdraw URI: ${talerWithdrawUri}`);
+ // waiting for the wallet:
+
+ const { data, error } = useSWR(
+ `integration-api/withdrawal-operation/${withdrawalId}`,
+ { refreshInterval: 1000 },
+ );
+
+ if (typeof error !== "undefined") {
+ logger.error(
+ `withdrawal (${withdrawalId}) was never (correctly) created at the bank...`,
+ error,
+ );
+ pageStateSetter((prevState: PageStateType) => ({
+ ...prevState,
+
+ error: {
+ title: i18n.str`withdrawal (${withdrawalId}) was never (correctly) created at the bank...`,
+ },
+ }));
+ return (
+
+
+
+ {abortButton}
+
+ );
+ }
+
+ // data didn't arrive yet and wallet didn't communicate:
+ if (typeof data === "undefined")
+ return
{i18n.str`Waiting the bank to create the operation...`}
;
+
+ /**
+ * Wallet didn't communicate withdrawal details yet:
+ */
+ logger.trace("withdrawal status", data);
+ if (data.aborted)
+ pageStateSetter((prevState: PageStateType) => {
+ const { withdrawalId, talerWithdrawUri, ...rest } = prevState;
+ return {
+ ...rest,
+ withdrawalInProgress: false,
+
+ error: {
+ title: i18n.str`This withdrawal was aborted!`,
+ },
+ };
+ });
+
+ if (!data.selection_done) {
+ return (
+
+ );
+ }
+ /**
+ * Wallet POSTed the withdrawal details! Ask the
+ * user to authorize the operation (here CAPTCHA).
+ */
+ return ;
+}
diff --git a/packages/demobank-ui/src/pages/home/AccountPage.tsx b/packages/demobank-ui/src/pages/home/AccountPage.tsx
deleted file mode 100644
index da22568f9..000000000
--- a/packages/demobank-ui/src/pages/home/AccountPage.tsx
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- 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, HttpStatusCode, Logger } from "@gnu-taler/taler-util";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { ComponentChildren, Fragment, h, VNode } from "preact";
-import { useEffect } from "preact/hooks";
-import useSWR, { SWRConfig, useSWRConfig } from "swr";
-import { useBackendContext } from "../../context/backend.js";
-import { PageStateType, usePageContext } from "../../context/pageState.js";
-import { BackendInfo } from "../../hooks/backend.js";
-import { bankUiSettings } from "../../settings.js";
-import { getIbanFromPayto, prepareHeaders } from "../../utils.js";
-import { BankFrame } from "./BankFrame.js";
-import { LoginForm } from "./LoginForm.js";
-import { PaymentOptions } from "./PaymentOptions.js";
-import { Transactions } from "./Transactions.js";
-import { WithdrawalQRCode } from "./WithdrawalQRCode.js";
-
-export function AccountPage(): VNode {
- const backend = useBackendContext();
- const { i18n } = useTranslationContext();
-
- if (backend.state.status === "loggedOut") {
- return (
-
-
{i18n.str`Welcome to ${bankUiSettings.bankName}!`}
-
-
- );
- }
-
- return (
-
-
-
- );
-}
-
-/**
- * Factor out login credentials.
- */
-function SWRWithCredentials({
- children,
- info,
-}: {
- children: ComponentChildren;
- info: BackendInfo;
-}): VNode {
- const { username, password, url: backendUrl } = info;
- const headers = prepareHeaders(username, password);
- return (
- {
- return fetch(new URL(url, backendUrl).href, { headers }).then((r) => {
- if (!r.ok) throw { status: r.status, json: r.json() };
-
- return r.json();
- });
- },
- }}
- >
- {children as any}
-
- );
-}
-
-const logger = new Logger("AccountPage");
-
-/**
- * Show only the account's balance. NOTE: the backend state
- * is mostly needed to provide the user's credentials to POST
- * to the bank.
- */
-function Account({ accountLabel }: { accountLabel: string }): VNode {
- const { cache } = useSWRConfig();
-
- // Getting the bank account balance:
- const endpoint = `access-api/accounts/${accountLabel}`;
- const { data, error, mutate } = useSWR(endpoint, {
- // refreshInterval: 0,
- // revalidateIfStale: false,
- // revalidateOnMount: false,
- // revalidateOnFocus: false,
- // revalidateOnReconnect: false,
- });
- const backend = useBackendContext();
- const { pageState, pageStateSetter: setPageState } = usePageContext();
- const { withdrawalId, talerWithdrawUri, timestamp } = pageState;
- const { i18n } = useTranslationContext();
- useEffect(() => {
- mutate();
- }, [timestamp]);
-
- /**
- * This part shows a list of transactions: with 5 elements by
- * default and offers a "load more" button.
- */
- // const [txPageNumber, setTxPageNumber] = useTransactionPageNumber();
- // const txsPages = [];
- // for (let i = 0; i <= txPageNumber; i++) {
- // txsPages.push();
- // }
-
- if (typeof error !== "undefined") {
- logger.error("account error", error, endpoint);
- /**
- * FIXME: to minimize the code, try only one invocation
- * of pageStateSetter, after having decided the error
- * message in the case-branch.
- */
- switch (error.status) {
- case 404: {
- backend.clear();
- setPageState((prevState: PageStateType) => ({
- ...prevState,
-
- error: {
- title: i18n.str`Username or account label '${accountLabel}' not found. Won't login.`,
- },
- }));
-
- /**
- * 404 should never stick to the cache, because they
- * taint successful future registrations. How? After
- * registering, the user gets navigated to this page,
- * therefore a previous 404 on this SWR key (the requested
- * resource) would still appear as valid and cause this
- * page not to be shown! A typical case is an attempted
- * login of a unregistered user X, and then a registration
- * attempt of the same user X: in this case, the failed
- * login would cache a 404 error to X's profile, resulting
- * in the legitimate request after the registration to still
- * be flagged as 404. Clearing the cache should prevent
- * this. */
- (cache as any).clear();
- return
;
- }
- default: {
- backend.clear();
- setPageState((prevState: PageStateType) => ({
- ...prevState,
- error: {
- title: i18n.str`Account information could not be retrieved.`,
- debug: JSON.stringify(error),
- },
- }));
- return
Unknown problem...
;
- }
- }
- }
- const balance = !data ? undefined : Amounts.parseOrThrow(data.balance.amount);
- const accountNumber = !data ? undefined : getIbanFromPayto(data.paytoUri);
- const balanceIsDebit = data && data.balance.credit_debit_indicator == "debit";
-
- /**
- * This block shows the withdrawal QR code.
- *
- * A withdrawal operation replaces everything in the page and
- * (ToDo:) starts polling the backend until either the wallet
- * selected a exchange and reserve public key, or a error / abort
- * happened.
- *
- * After reaching one of the above states, the user should be
- * brought to this ("Account") page where they get informed about
- * the outcome.
- */
- if (talerWithdrawUri && withdrawalId) {
- logger.trace("Bank created a new Taler withdrawal");
- return (
-
-
-
- );
- }
- const balanceValue = !balance ? undefined : Amounts.stringifyValue(balance);
-
- return (
-
-
-
-
-
-
- );
-}
-
-// function useTransactionPageNumber(): [number, StateUpdater] {
-// const ret = useNotNullLocalStorage("transaction-page", "0");
-// const retObj = JSON.parse(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];
-// }
diff --git a/packages/demobank-ui/src/pages/home/BankFrame.tsx b/packages/demobank-ui/src/pages/home/BankFrame.tsx
deleted file mode 100644
index cb629effb..000000000
--- a/packages/demobank-ui/src/pages/home/BankFrame.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- 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 { Logger } from "@gnu-taler/taler-util";
-import { ComponentChildren, Fragment, h, VNode } from "preact";
-import talerLogo from "../../assets/logo-white.svg";
-import { LangSelectorLikePy as LangSelector } from "../../components/menu/LangSelector.js";
-import { useBackendContext } from "../../context/backend.js";
-import { PageStateType, usePageContext } from "../../context/pageState.js";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { bankUiSettings } from "../../settings.js";
-
-const logger = new Logger("BankFrame");
-
-export function BankFrame({
- children,
-}: {
- children: ComponentChildren;
-}): VNode {
- const { i18n } = useTranslationContext();
- const backend = useBackendContext();
- const { pageState, pageStateSetter } = usePageContext();
- logger.trace("state", pageState);
- const logOut = (
-
-
- This part of the demo shows how a bank that supports Taler
- directly would work. In addition to using your own bank account,
- you can also see the transaction history of some{" "}
- Public Accounts.
-
-
- );
- return rval;
-}
diff --git a/packages/demobank-ui/src/pages/home/LoginForm.tsx b/packages/demobank-ui/src/pages/home/LoginForm.tsx
deleted file mode 100644
index 4f38bc91d..000000000
--- a/packages/demobank-ui/src/pages/home/LoginForm.tsx
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- 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 { h, VNode } from "preact";
-import { route } from "preact-router";
-import { useEffect, useRef, useState } from "preact/hooks";
-import { useBackendContext } from "../../context/backend.js";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { BackendStateHandler } from "../../hooks/backend.js";
-import { bankUiSettings } from "../../settings.js";
-import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
-
-/**
- * Collect and submit login data.
- */
-export function LoginForm(): VNode {
- const backend = useBackendContext();
- const [username, setUsername] = useState();
- const [password, setPassword] = useState();
- const { i18n } = useTranslationContext();
- const ref = useRef(null);
- useEffect(() => {
- ref.current?.focus();
- }, []);
-
- const errors = undefinedIfEmpty({
- username: !username ? i18n.str`Missing username` : undefined,
- password: !password ? i18n.str`Missing password` : undefined,
- });
-
- return (
-
-
-
- );
-}
-
-async function loginCall(
- req: { username: string; password: string },
- /**
- * FIXME: figure out if the two following
- * functions can be retrieved from the state.
- */
- backend: BackendStateHandler,
-): Promise {
- /**
- * Optimistically setting the state as 'logged in', and
- * let the Account component request the balance to check
- * whether the credentials are valid. */
-
- backend.save({
- url: getBankBackendBaseUrl(),
- username: req.username,
- password: req.password,
- });
-}
diff --git a/packages/demobank-ui/src/pages/home/PaymentOptions.tsx b/packages/demobank-ui/src/pages/home/PaymentOptions.tsx
deleted file mode 100644
index ae876d556..000000000
--- a/packages/demobank-ui/src/pages/home/PaymentOptions.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- 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 { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
-import { WalletWithdrawForm } from "./WalletWithdrawForm.js";
-
-/**
- * Let the user choose a payment option,
- * then specify the details trigger the action.
- */
-export function PaymentOptions({ currency }: { currency?: string }): VNode {
- const { i18n } = useTranslationContext();
-
- const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">(
- "charge-wallet",
- );
-
- return (
-
-
-
-
-
-
- {tab === "charge-wallet" && (
-
-
{i18n.str`Obtain digital cash`}
-
-
- )}
- {tab === "wire-transfer" && (
-
-
{i18n.str`Transfer to bank account`}
-
-
- )}
-
-
- );
-}
diff --git a/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx
deleted file mode 100644
index 36182cd93..000000000
--- a/packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- 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 (
-
- );
-}
-
-/**
- * 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();
-}
diff --git a/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx b/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
deleted file mode 100644
index 1577e0dfc..000000000
--- a/packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- 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 { Logger } from "@gnu-taler/taler-util";
-import { useLocalStorage } from "@gnu-taler/web-util/lib/index.browser";
-import { ComponentChildren, Fragment, h, VNode } from "preact";
-import { route } from "preact-router";
-import { StateUpdater } from "preact/hooks";
-import useSWR, { SWRConfig } from "swr";
-import { PageStateType, usePageContext } from "../../context/pageState.js";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { getBankBackendBaseUrl } from "../../utils.js";
-import { BankFrame } from "./BankFrame.js";
-import { Transactions } from "./Transactions.js";
-
-const logger = new Logger("PublicHistoriesPage");
-
-export function PublicHistoriesPage(): VNode {
- return (
-
-
-
-
-
- );
-}
-
-function SWRWithoutCredentials({
- baseUrl,
- children,
-}: {
- children: ComponentChildren;
- baseUrl: string;
-}): VNode {
- logger.trace("Base URL", baseUrl);
- return (
-
- fetch(baseUrl + url || "").then((r) => {
- if (!r.ok) throw { status: r.status, json: r.json() };
-
- return r.json();
- }),
- }}
- >
- {children as any}
-
- );
-}
-
-/**
- * Show histories of public accounts.
- */
-function PublicHistories(): VNode {
- const { pageState, pageStateSetter } = usePageContext();
- const [showAccount, setShowAccount] = useShowPublicAccount();
- const { data, error } = useSWR("access-api/public-accounts");
- const { i18n } = useTranslationContext();
-
- if (typeof error !== "undefined") {
- switch (error.status) {
- case 404:
- logger.error("public accounts: 404", error);
- route("/account");
- pageStateSetter((prevState: PageStateType) => ({
- ...prevState,
-
- error: {
- title: i18n.str`List of public accounts was not found.`,
- debug: JSON.stringify(error),
- },
- }));
- break;
- default:
- logger.error("public accounts: non-404 error", error);
- route("/account");
- pageStateSetter((prevState: PageStateType) => ({
- ...prevState,
-
- error: {
- title: i18n.str`List of public accounts could not be retrieved.`,
- debug: JSON.stringify(error),
- },
- }));
- break;
- }
- }
- if (!data) return
Waiting public accounts list...
;
- const txs: Record = {};
- const accountsBar = [];
-
- /**
- * Show the account specified in the props, or just one
- * from the list if that's not given.
- */
- if (typeof showAccount === "undefined" && data.publicAccounts.length > 0) {
- setShowAccount(data.publicAccounts[1].accountLabel);
- }
- logger.trace(`Public history tab: ${showAccount}`);
-
- // Ask story of all the public accounts.
- for (const account of data.publicAccounts) {
- logger.trace("Asking transactions for", account.accountLabel);
- const isSelected = account.accountLabel == showAccount;
- accountsBar.push(
-
-
-
-
- );
-}
-
-/**
- * Stores in the state a object containing a 'username'
- * and 'password' field, in order to avoid losing the
- * handle of the data entered by the user in fields.
- */
-function useShowPublicAccount(
- state?: string,
-): [string | undefined, StateUpdater] {
- const ret = useLocalStorage("show-public-account", JSON.stringify(state));
- const retObj: string | undefined = 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];
-}
diff --git a/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx b/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx
deleted file mode 100644
index 521d4255e..000000000
--- a/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- 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
- */
-
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { QrCodeSection } from "./QrCodeSection.js";
-
-export default {
- title: "Qr Code Selection",
-};
-
-export const SimpleExample = {
- component: QrCodeSection,
- props: {
- talerWithdrawUri: "taler://withdraw/asdasdasd",
- },
-};
diff --git a/packages/demobank-ui/src/pages/home/QrCodeSection.tsx b/packages/demobank-ui/src/pages/home/QrCodeSection.tsx
deleted file mode 100644
index 7e89cd516..000000000
--- a/packages/demobank-ui/src/pages/home/QrCodeSection.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- 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 { h, VNode } from "preact";
-import { useEffect } from "preact/hooks";
-import { QR } from "../../components/QR.js";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-
-export function QrCodeSection({
- talerWithdrawUri,
- abortButton,
-}: {
- talerWithdrawUri: string;
- abortButton: h.JSX.Element;
-}): VNode {
- const { i18n } = useTranslationContext();
- useEffect(() => {
- //Taler Wallet WebExtension is listening to headers response and tab updates.
- //In the SPA there is no header response with the Taler URI so
- //this hack manually triggers the tab update after the QR is in the DOM.
- // WebExtension will be using
- // https://developer.chrome.com/docs/extensions/reference/tabs/#event-onUpdated
- document.title = `${document.title} ${talerWithdrawUri}`;
- }, []);
-
- return (
-
-
{i18n.str`Transfer to Taler Wallet`}
-
-
-
{i18n.str`Use this QR code to withdraw to your mobile wallet:`}
-
-
- );
-}
diff --git a/packages/demobank-ui/src/pages/home/RegistrationPage.tsx b/packages/demobank-ui/src/pages/home/RegistrationPage.tsx
deleted file mode 100644
index c21693885..000000000
--- a/packages/demobank-ui/src/pages/home/RegistrationPage.tsx
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- 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 { Logger } from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import { route } from "preact-router";
-import { StateUpdater, 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 { BackendStateHandler } from "../../hooks/backend.js";
-import { bankUiSettings } from "../../settings.js";
-import { getBankBackendBaseUrl, undefinedIfEmpty } from "../../utils.js";
-import { BankFrame } from "./BankFrame.js";
-import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
-
-const logger = new Logger("RegistrationPage");
-
-export function RegistrationPage(): VNode {
- const { i18n } = useTranslationContext();
- if (!bankUiSettings.allowRegistrations) {
- return (
-
-
{i18n.str`Currently, the bank is not accepting new registrations!`}
{i18n.str`Welcome to ${bankUiSettings.bankName}!`}
-
-
-
-
-
-
- );
-}
-
-/**
- * This function requests /register.
- *
- * This function is responsible to change two states:
- * the backend's (to store the login credentials) and
- * the page's (to indicate a successful login or a problem).
- */
-async function registrationCall(
- req: { username: string; password: string },
- /**
- * FIXME: figure out if the two following
- * functions can be retrieved somewhat from
- * the state.
- */
- backend: BackendStateHandler,
- pageStateSetter: StateUpdater,
- i18n: InternationalizationAPI,
-): Promise {
- const url = getBankBackendBaseUrl();
-
- const headers = new Headers();
- headers.append("Content-Type", "application/json");
- const registerEndpoint = new URL("access-api/testing/register", url);
- let res: Response;
- try {
- res = await fetch(registerEndpoint.href, {
- method: "POST",
- body: JSON.stringify({
- username: req.username,
- password: req.password,
- }),
- headers,
- });
- } catch (error) {
- logger.error(
- `Could not POST new registration to the bank (${registerEndpoint.href})`,
- error,
- );
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`Registration failed, please report`,
- debug: JSON.stringify(error),
- },
- }));
- return;
- }
- if (!res.ok) {
- const response = await res.json();
- if (res.status === 409) {
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`That username is already taken`,
- debug: JSON.stringify(response),
- },
- }));
- } else {
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`New registration gave response error`,
- debug: JSON.stringify(response),
- },
- }));
- }
- } else {
- // registration was ok
- backend.save({
- url,
- username: req.username,
- password: req.password,
- });
- route("/account");
- }
-}
diff --git a/packages/demobank-ui/src/pages/home/ShowInputErrorLabel.tsx b/packages/demobank-ui/src/pages/home/ShowInputErrorLabel.tsx
deleted file mode 100644
index dacffe20a..000000000
--- a/packages/demobank-ui/src/pages/home/ShowInputErrorLabel.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- 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 { Fragment, h, VNode } from "preact";
-
-export function ShowInputErrorLabel({
- isDirty,
- message,
-}: {
- message: string | undefined;
- isDirty: boolean;
-}): VNode {
- if (message && isDirty)
- return
{message}
;
- return ;
-}
diff --git a/packages/demobank-ui/src/pages/home/Transactions.tsx b/packages/demobank-ui/src/pages/home/Transactions.tsx
deleted file mode 100644
index ca88abd4d..000000000
--- a/packages/demobank-ui/src/pages/home/Transactions.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- 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 { Logger } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
-import { useEffect } from "preact/hooks";
-import useSWR from "swr";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-
-const logger = new Logger("Transactions");
-/**
- * Show one page of transactions.
- */
-export function Transactions({
- pageNumber,
- accountLabel,
- balanceValue,
-}: {
- pageNumber: number;
- accountLabel: string;
- balanceValue?: string;
-}): VNode {
- const { i18n } = useTranslationContext();
- const { data, error, mutate } = useSWR(
- `access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
- );
- useEffect(() => {
- if (balanceValue) {
- mutate();
- }
- }, [balanceValue ?? ""]);
- if (typeof error !== "undefined") {
- logger.error("transactions not found error", error);
- switch (error.status) {
- case 404: {
- return
Transactions page {pageNumber} was not found.
;
- }
- case 401: {
- return
Wrong credentials given.
;
- }
- default: {
- return
Transaction page {pageNumber} could not be retrieved.
;
- }
- }
- }
- if (!data) {
- logger.trace(`History data of ${accountLabel} not arrived`);
- return
Transactions page loading...
;
- }
- logger.trace(`History data of ${accountLabel}`, data);
- return (
-
- );
-}
diff --git a/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx
deleted file mode 100644
index 440d16c6c..000000000
--- a/packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- 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 { 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(null);
- useEffect(() => {
- if (focus) ref.current?.focus();
- }, [focus]);
- return (
-
- );
-}
-
-/**
- * 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,
- i18n: InternationalizationAPI,
-): Promise {
- 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,
- }));
-}
diff --git a/packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx
deleted file mode 100644
index 693f85bcd..000000000
--- a/packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- 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 { Logger } from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import { StateUpdater } 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 } from "../../utils.js";
-
-const logger = new Logger("WithdrawalConfirmationQuestion");
-
-/**
- * Additional authentication required to complete the operation.
- * Not providing a back button, only abort.
- */
-export function WithdrawalConfirmationQuestion(): VNode {
- const { pageState, pageStateSetter } = usePageContext();
- const backend = useBackendContext();
- const { i18n } = useTranslationContext();
- const captchaNumbers = {
- a: Math.floor(Math.random() * 10),
- b: Math.floor(Math.random() * 10),
- };
- let captchaAnswer = "";
-
- return (
-
-
{i18n.str`Confirm Withdrawal`}
-
-
-
-
-
-
- A this point, a real bank would ask for an additional
- authentication proof (PIN/TAN, one time password, ..), instead
- of a simple calculation.
-
-
-
-
-
-
- );
-}
-
-/**
- * This function confirms a withdrawal operation AFTER
- * the wallet has given the exchange's payment details
- * to the bank (via the Integration API). Such details
- * can be given by scanning a QR code or by passing the
- * raw taler://withdraw-URI to the CLI wallet.
- *
- * This function will set the confirmation status in the
- * 'page state' and let the related components refresh.
- */
-async function confirmWithdrawalCall(
- backendState: BackendState,
- withdrawalId: string | undefined,
- pageStateSetter: StateUpdater,
- i18n: InternationalizationAPI,
-): Promise {
- if (backendState.status === "loggedOut") {
- logger.error("No credentials found.");
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`No credentials found.`,
- },
- }));
- return;
- }
- if (typeof withdrawalId === "undefined") {
- logger.error("No withdrawal ID found.");
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`No withdrawal ID found.`,
- },
- }));
- return;
- }
- let res: Response;
- try {
- const { username, password } = backendState;
- const headers = prepareHeaders(username, password);
- /**
- * NOTE: tests show that when a same object is being
- * POSTed, caching might prevent same requests from being
- * made. Hence, trying to POST twice the same amount might
- * get silently ignored.
- *
- * headers.append("cache-control", "no-store");
- * headers.append("cache-control", "no-cache");
- * headers.append("pragma", "no-cache");
- * */
-
- // Backend URL must have been stored _with_ a final slash.
- const url = new URL(
- `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/confirm`,
- backendState.url,
- );
- res = await fetch(url.href, {
- method: "POST",
- headers,
- });
- } catch (error) {
- logger.error("Could not POST withdrawal confirmation to the bank", error);
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`Could not confirm the withdrawal`,
- description: (error as any).error.description,
- debug: JSON.stringify(error),
- },
- }));
- return;
- }
- if (!res || !res.ok) {
- const response = await res.json();
- // assume not ok if res is null
- logger.error(
- `Withdrawal confirmation gave response error (${res.status})`,
- res.statusText,
- );
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`Withdrawal confirmation gave response error`,
- debug: JSON.stringify(response),
- },
- }));
- return;
- }
- logger.trace("Withdrawal operation confirmed!");
- pageStateSetter((prevState) => {
- const { talerWithdrawUri, ...rest } = prevState;
- return {
- ...rest,
-
- info: i18n.str`Withdrawal confirmed!`,
- };
- });
-}
-
-/**
- * Abort a withdrawal operation via the Access API's /abort.
- */
-async function abortWithdrawalCall(
- backendState: BackendState,
- withdrawalId: string | undefined,
- pageStateSetter: StateUpdater,
- i18n: InternationalizationAPI,
-): Promise {
- if (backendState.status === "loggedOut") {
- logger.error("No credentials found.");
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`No credentials found.`,
- },
- }));
- return;
- }
- if (typeof withdrawalId === "undefined") {
- logger.error("No withdrawal ID found.");
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`No withdrawal ID found.`,
- },
- }));
- return;
- }
- let res: Response;
- try {
- const { username, password } = backendState;
- const headers = prepareHeaders(username, password);
- /**
- * NOTE: tests show that when a same object is being
- * POSTed, caching might prevent same requests from being
- * made. Hence, trying to POST twice the same amount might
- * get silently ignored. Needs more observation!
- *
- * headers.append("cache-control", "no-store");
- * headers.append("cache-control", "no-cache");
- * headers.append("pragma", "no-cache");
- * */
-
- // Backend URL must have been stored _with_ a final slash.
- const url = new URL(
- `access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/abort`,
- backendState.url,
- );
- res = await fetch(url.href, { method: "POST", headers });
- } catch (error) {
- logger.error("Could not abort the withdrawal", error);
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`Could not abort the withdrawal.`,
- description: (error as any).error.description,
- debug: JSON.stringify(error),
- },
- }));
- return;
- }
- if (!res.ok) {
- const response = await res.json();
- logger.error(
- `Withdrawal abort gave response error (${res.status})`,
- res.statusText,
- );
- pageStateSetter((prevState) => ({
- ...prevState,
-
- error: {
- title: i18n.str`Withdrawal abortion failed.`,
- description: response.error.description,
- debug: JSON.stringify(response),
- },
- }));
- return;
- }
- logger.trace("Withdrawal operation aborted!");
- pageStateSetter((prevState) => {
- const { ...rest } = prevState;
- return {
- ...rest,
-
- info: i18n.str`Withdrawal aborted!`,
- };
- });
-}
diff --git a/packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx
deleted file mode 100644
index 978d3a840..000000000
--- a/packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- 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 { Logger } from "@gnu-taler/taler-util";
-import { Fragment, h, VNode } from "preact";
-import useSWR from "swr";
-import { PageStateType, usePageContext } from "../../context/pageState.js";
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
-import { QrCodeSection } from "./QrCodeSection.js";
-import { WithdrawalConfirmationQuestion } from "./WithdrawalConfirmationQuestion.js";
-
-const logger = new Logger("WithdrawalQRCode");
-/**
- * Offer the QR code (and a clickable taler://-link) to
- * permit the passing of exchange and reserve details to
- * the bank. Poll the backend until such operation is done.
- */
-export function WithdrawalQRCode({
- withdrawalId,
- talerWithdrawUri,
-}: {
- withdrawalId: string;
- talerWithdrawUri: string;
-}): VNode {
- // turns true when the wallet POSTed the reserve details:
- const { pageState, pageStateSetter } = usePageContext();
- const { i18n } = useTranslationContext();
- const abortButton = (
- {
- pageStateSetter((prevState: PageStateType) => {
- return {
- ...prevState,
- withdrawalId: undefined,
- talerWithdrawUri: undefined,
- withdrawalInProgress: false,
- };
- });
- }}
- >{i18n.str`Abort`}
- );
-
- logger.trace(`Showing withdraw URI: ${talerWithdrawUri}`);
- // waiting for the wallet:
-
- const { data, error } = useSWR(
- `integration-api/withdrawal-operation/${withdrawalId}`,
- { refreshInterval: 1000 },
- );
-
- if (typeof error !== "undefined") {
- logger.error(
- `withdrawal (${withdrawalId}) was never (correctly) created at the bank...`,
- error,
- );
- pageStateSetter((prevState: PageStateType) => ({
- ...prevState,
-
- error: {
- title: i18n.str`withdrawal (${withdrawalId}) was never (correctly) created at the bank...`,
- },
- }));
- return (
-
-
-
- {abortButton}
-
- );
- }
-
- // data didn't arrive yet and wallet didn't communicate:
- if (typeof data === "undefined")
- return
{i18n.str`Waiting the bank to create the operation...`}
;
-
- /**
- * Wallet didn't communicate withdrawal details yet:
- */
- logger.trace("withdrawal status", data);
- if (data.aborted)
- pageStateSetter((prevState: PageStateType) => {
- const { withdrawalId, talerWithdrawUri, ...rest } = prevState;
- return {
- ...rest,
- withdrawalInProgress: false,
-
- error: {
- title: i18n.str`This withdrawal was aborted!`,
- },
- };
- });
-
- if (!data.selection_done) {
- return (
-
- );
- }
- /**
- * Wallet POSTed the withdrawal details! Ask the
- * user to authorize the operation (here CAPTCHA).
- */
- return ;
-}
diff --git a/packages/demobank-ui/src/pages/home/index.stories.tsx b/packages/demobank-ui/src/pages/home/index.stories.tsx
deleted file mode 100644
index 1c4c64cdb..000000000
--- a/packages/demobank-ui/src/pages/home/index.stories.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- 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
- */
-
-export * as qr from "./QrCodeSection.stories.js";
diff --git a/packages/demobank-ui/src/pages/index.stories.tsx b/packages/demobank-ui/src/pages/index.stories.tsx
new file mode 100644
index 000000000..1c4c64cdb
--- /dev/null
+++ b/packages/demobank-ui/src/pages/index.stories.tsx
@@ -0,0 +1,17 @@
+/*
+ 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
+ */
+
+export * as qr from "./QrCodeSection.stories.js";
diff --git a/packages/demobank-ui/src/stories.tsx b/packages/demobank-ui/src/stories.tsx
index aab93a749..611d63879 100644
--- a/packages/demobank-ui/src/stories.tsx
+++ b/packages/demobank-ui/src/stories.tsx
@@ -20,7 +20,7 @@
*/
import { strings } from "./i18n/strings.js";
-import * as pages from "./pages/home/index.stories.js";
+import * as pages from "./pages/index.stories.js";
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
--
cgit v1.2.3