From a8c5a9696c1735a178158cbc9ac4f9bb4b6f013d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 8 Feb 2023 17:41:19 -0300 Subject: impl accout management and refactor --- packages/demobank-ui/src/pages/AccountPage.tsx | 283 +++++++------------------ 1 file changed, 77 insertions(+), 206 deletions(-) (limited to 'packages/demobank-ui/src/pages/AccountPage.tsx') diff --git a/packages/demobank-ui/src/pages/AccountPage.tsx b/packages/demobank-ui/src/pages/AccountPage.tsx index 8d29bd933..769e85804 100644 --- a/packages/demobank-ui/src/pages/AccountPage.tsx +++ b/packages/demobank-ui/src/pages/AccountPage.tsx @@ -14,206 +14,52 @@ 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 { Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; +import { + HttpResponsePaginated, + useTranslationContext, +} from "@gnu-taler/web-util/lib/index.browser"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Cashouts } from "../components/Cashouts/index.js"; import { Transactions } from "../components/Transactions/index.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() }; +import { useAccountDetails } from "../hooks/access.js"; +import { PaymentOptions } from "./PaymentOptions.js"; - return r.json(); - }); - }, - }} - > - {children as any} - - ); +interface Props { + account: string; + onLoadNotOk: (error: HttpResponsePaginated) => VNode; } - -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. + * Query account information and show QR code if there is pending withdrawal */ -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; +export function AccountPage({ account, onLoadNotOk }: Props): VNode { + const result = useAccountDetails(account); 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...

; - } - } + if (!result.ok) { + return onLoadNotOk(result); } - const balance = !data ? undefined : Amounts.parse(data.balance.amount); - const errorParsingBalance = data && !balance; - 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"); + const { data } = result; + const balance = Amounts.parse(data.balance.amount); + const errorParsingBalance = !balance; + const payto = parsePaytoUri(data.paytoUri); + if (!payto || !payto.isKnown || payto.targetType !== "iban") { return ( - - - +
Payto from server is not valid "{data.paytoUri}"
); } - const balanceValue = !balance ? undefined : Amounts.stringifyValue(balance); + const accountNumber = payto.iban; + const balanceIsDebit = data.balance.credit_debit_indicator == "debit"; return ( - +

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

@@ -239,7 +85,10 @@ function Account({ accountLabel }: { accountLabel: string }): VNode { ) : (
{balanceIsDebit ? - : null} - {`${balanceValue}`}  + {`${Amounts.stringifyValue( + balance, + )}`} +   {`${balance.currency}`}
)} @@ -248,34 +97,56 @@ function Account({ accountLabel }: { accountLabel: string }): VNode {

{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]; -// } +function Moves({ account }: { account: string }): VNode { + const [tab, setTab] = useState<"transactions" | "cashouts">("transactions"); + const { i18n } = useTranslationContext(); + return ( +
+
+
+ + +
+ {tab === "transactions" && ( +
+

{i18n.str`Latest transactions`}

+ +
+ )} + {tab === "cashouts" && ( +
+

{i18n.str`Latest cashouts`}

+ +
+ )} +
+
+ ); +} -- cgit v1.2.3