From 5fc8f95a5d4ce8dea03b2dbec7eb5c37e7ff3f15 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 9 Dec 2022 12:15:15 -0300 Subject: simplify directories --- .../demobank-ui/src/components/LangSelector.tsx | 103 +++++ .../src/components/menu/LangSelector.tsx | 103 ----- packages/demobank-ui/src/pages/AccountPage.tsx | 266 +++++++++++++ packages/demobank-ui/src/pages/BankFrame.tsx | 192 +++++++++ packages/demobank-ui/src/pages/LoginForm.tsx | 139 +++++++ packages/demobank-ui/src/pages/PaymentOptions.tsx | 70 ++++ .../src/pages/PaytoWireTransferForm.tsx | 432 +++++++++++++++++++++ .../demobank-ui/src/pages/PublicHistoriesPage.tsx | 182 +++++++++ .../src/pages/QrCodeSection.stories.tsx | 33 ++ packages/demobank-ui/src/pages/QrCodeSection.tsx | 59 +++ .../demobank-ui/src/pages/RegistrationPage.tsx | 262 +++++++++++++ packages/demobank-ui/src/pages/Routing.tsx | 6 +- .../demobank-ui/src/pages/ShowInputErrorLabel.tsx | 29 ++ packages/demobank-ui/src/pages/Transactions.tsx | 106 +++++ .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 187 +++++++++ .../src/pages/WithdrawalConfirmationQuestion.tsx | 327 ++++++++++++++++ .../demobank-ui/src/pages/WithdrawalQRCode.tsx | 120 ++++++ .../demobank-ui/src/pages/home/AccountPage.tsx | 266 ------------- packages/demobank-ui/src/pages/home/BankFrame.tsx | 192 --------- packages/demobank-ui/src/pages/home/LoginForm.tsx | 139 ------- .../demobank-ui/src/pages/home/PaymentOptions.tsx | 70 ---- .../src/pages/home/PaytoWireTransferForm.tsx | 432 --------------------- .../src/pages/home/PublicHistoriesPage.tsx | 182 --------- .../src/pages/home/QrCodeSection.stories.tsx | 33 -- .../demobank-ui/src/pages/home/QrCodeSection.tsx | 59 --- .../src/pages/home/RegistrationPage.tsx | 262 ------------- .../src/pages/home/ShowInputErrorLabel.tsx | 29 -- .../demobank-ui/src/pages/home/Transactions.tsx | 106 ----- .../src/pages/home/WalletWithdrawForm.tsx | 187 --------- .../pages/home/WithdrawalConfirmationQuestion.tsx | 327 ---------------- .../src/pages/home/WithdrawalQRCode.tsx | 120 ------ .../demobank-ui/src/pages/home/index.stories.tsx | 17 - packages/demobank-ui/src/pages/index.stories.tsx | 17 + packages/demobank-ui/src/stories.tsx | 2 +- 34 files changed, 2528 insertions(+), 2528 deletions(-) create mode 100644 packages/demobank-ui/src/components/LangSelector.tsx delete mode 100644 packages/demobank-ui/src/components/menu/LangSelector.tsx create mode 100644 packages/demobank-ui/src/pages/AccountPage.tsx create mode 100644 packages/demobank-ui/src/pages/BankFrame.tsx create mode 100644 packages/demobank-ui/src/pages/LoginForm.tsx create mode 100644 packages/demobank-ui/src/pages/PaymentOptions.tsx create mode 100644 packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx create mode 100644 packages/demobank-ui/src/pages/PublicHistoriesPage.tsx create mode 100644 packages/demobank-ui/src/pages/QrCodeSection.stories.tsx create mode 100644 packages/demobank-ui/src/pages/QrCodeSection.tsx create mode 100644 packages/demobank-ui/src/pages/RegistrationPage.tsx create mode 100644 packages/demobank-ui/src/pages/ShowInputErrorLabel.tsx create mode 100644 packages/demobank-ui/src/pages/Transactions.tsx create mode 100644 packages/demobank-ui/src/pages/WalletWithdrawForm.tsx create mode 100644 packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx create mode 100644 packages/demobank-ui/src/pages/WithdrawalQRCode.tsx delete mode 100644 packages/demobank-ui/src/pages/home/AccountPage.tsx delete mode 100644 packages/demobank-ui/src/pages/home/BankFrame.tsx delete mode 100644 packages/demobank-ui/src/pages/home/LoginForm.tsx delete mode 100644 packages/demobank-ui/src/pages/home/PaymentOptions.tsx delete mode 100644 packages/demobank-ui/src/pages/home/PaytoWireTransferForm.tsx delete mode 100644 packages/demobank-ui/src/pages/home/PublicHistoriesPage.tsx delete mode 100644 packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx delete mode 100644 packages/demobank-ui/src/pages/home/QrCodeSection.tsx delete mode 100644 packages/demobank-ui/src/pages/home/RegistrationPage.tsx delete mode 100644 packages/demobank-ui/src/pages/home/ShowInputErrorLabel.tsx delete mode 100644 packages/demobank-ui/src/pages/home/Transactions.tsx delete mode 100644 packages/demobank-ui/src/pages/home/WalletWithdrawForm.tsx delete mode 100644 packages/demobank-ui/src/pages/home/WithdrawalConfirmationQuestion.tsx delete mode 100644 packages/demobank-ui/src/pages/home/WithdrawalQRCode.tsx delete mode 100644 packages/demobank-ui/src/pages/home/index.stories.tsx create mode 100644 packages/demobank-ui/src/pages/index.stories.tsx diff --git a/packages/demobank-ui/src/components/LangSelector.tsx b/packages/demobank-ui/src/components/LangSelector.tsx new file mode 100644 index 000000000..574f42814 --- /dev/null +++ b/packages/demobank-ui/src/components/LangSelector.tsx @@ -0,0 +1,103 @@ +/* + 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/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

Profile not found...

; + } + case HttpStatusCode.Unauthorized: + case HttpStatusCode.Forbidden: { + backend.clear(); + setPageState((prevState: PageStateType) => ({ + ...prevState, + error: { + title: i18n.str`Wrong credentials given.`, + }, + })); + return

Wrong credentials...

; + } + 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 ( + +
+

+ + Welcome, + {accountNumber + ? `${accountLabel} (${accountNumber})` + : accountLabel} + ! + +

+
+
+
+

{i18n.str`Bank account balance`}

+ {!balance ? ( +
+ Waiting server response... +
+ ) : ( +
+ {balanceIsDebit ? - : null} + {`${balanceValue}`}  + {`${balance.currency}`} +
+ )} +
+
+
+
+

{i18n.str`Payments`}

+ +
+
+
+
+

{i18n.str`Latest transactions:`}

+ +
+
+
+ ); +} + +// 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 = ( + + ); + + const demo_sites = []; + for (const i in bankUiSettings.demoSites) + demo_sites.push( + + {bankUiSettings.demoSites[i][0]} + , + ); + + return ( + +
+ +
+

+ + {bankUiSettings.bankName} + +

+ {maybeDemoContent( +

+ + 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. + +

, + )} +
+ + {i18n.str`Taler + +
+ +
+ + + {backend.state.status === "loggedIn" ? logOut : null} + {children} +
+ +
+ ); +} + +function maybeDemoContent(content: VNode): VNode { + if (bankUiSettings.showDemoNav) { + return content; + } + return ; +} + +function ErrorBanner(): VNode | null { + const { pageState, pageStateSetter } = usePageContext(); + + if (!pageState.error) return null; + + const rval = ( +
+
+

+ {pageState.error.title} +

+
+ { + pageStateSetter((prev) => ({ ...prev, error: undefined })); + }} + /> +
+
+

{pageState.error.description}

+
+ ); + delete pageState.error; + return rval; +} + +function StatusBanner(): VNode | null { + const { pageState, pageStateSetter } = usePageContext(); + if (!pageState.info) return null; + + const rval = ( +
+
+

+ {pageState.info} +

+
+ { + pageStateSetter((prev) => ({ ...prev, info: undefined })); + }} + /> +
+
+
+ ); + 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 ( +