aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/src/pages/AccountPage.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/demobank-ui/src/pages/AccountPage.tsx')
-rw-r--r--packages/demobank-ui/src/pages/AccountPage.tsx283
1 files changed, 77 insertions, 206 deletions
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 <http://www.gnu.org/licenses/>
*/
-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 (
- <BankFrame>
- <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
- <LoginForm />
- </BankFrame>
- );
- }
-
- return (
- <SWRWithCredentials info={backend.state}>
- <Account accountLabel={backend.state.username} />
- </SWRWithCredentials>
- );
-}
-
-/**
- * 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 (
- <SWRConfig
- value={{
- fetcher: (url: string) => {
- 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}
- </SWRConfig>
- );
+interface Props {
+ account: string;
+ onLoadNotOk: <T, E>(error: HttpResponsePaginated<T, E>) => 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(<Transactions accountLabel={accountLabel} pageNumber={i} />);
- // }
-
- 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 <p>Profile not found...</p>;
- }
- case HttpStatusCode.Unauthorized:
- case HttpStatusCode.Forbidden: {
- backend.clear();
- setPageState((prevState: PageStateType) => ({
- ...prevState,
- error: {
- title: i18n.str`Wrong credentials given.`,
- },
- }));
- return <p>Wrong credentials...</p>;
- }
- default: {
- backend.clear();
- setPageState((prevState: PageStateType) => ({
- ...prevState,
- error: {
- title: i18n.str`Account information could not be retrieved.`,
- debug: JSON.stringify(error),
- },
- }));
- return <p>Unknown problem...</p>;
- }
- }
+ 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 (
- <BankFrame>
- <WithdrawalQRCode
- withdrawalId={withdrawalId}
- talerWithdrawUri={talerWithdrawUri}
- />
- </BankFrame>
+ <div>Payto from server is not valid &quot;{data.paytoUri}&quot;</div>
);
}
- const balanceValue = !balance ? undefined : Amounts.stringifyValue(balance);
+ const accountNumber = payto.iban;
+ const balanceIsDebit = data.balance.credit_debit_indicator == "debit";
return (
- <BankFrame>
+ <Fragment>
<div>
<h1 class="nav welcome-text">
<i18n.Translate>
Welcome,
- {accountNumber
- ? `${accountLabel} (${accountNumber})`
- : accountLabel}
- !
+ {accountNumber ? `${account} (${accountNumber})` : account}!
</i18n.Translate>
</h1>
</div>
@@ -239,7 +85,10 @@ function Account({ accountLabel }: { accountLabel: string }): VNode {
) : (
<div class="large-amount amount">
{balanceIsDebit ? <b>-</b> : null}
- <span class="value">{`${balanceValue}`}</span>&nbsp;
+ <span class="value">{`${Amounts.stringifyValue(
+ balance,
+ )}`}</span>
+ &nbsp;
<span class="currency">{`${balance.currency}`}</span>
</div>
)}
@@ -248,34 +97,56 @@ function Account({ accountLabel }: { accountLabel: string }): VNode {
<section id="payments">
<div class="payments">
<h2>{i18n.str`Payments`}</h2>
- <PaymentOptions currency={balance?.currency} />
+ <PaymentOptions currency={balance.currency} />
</div>
</section>
</Fragment>
)}
- <section id="main">
- <article>
- <h2>{i18n.str`Latest transactions:`}</h2>
- <Transactions
- balanceValue={balanceValue}
- pageNumber={0}
- accountLabel={accountLabel}
- />
- </article>
+
+ <section style={{ marginTop: "2em" }}>
+ <Moves account={account} />
</section>
- </BankFrame>
+ </Fragment>
);
}
-// function useTransactionPageNumber(): [number, StateUpdater<number>] {
-// const ret = useNotNullLocalStorage("transaction-page", "0");
-// const retObj = JSON.parse(ret[0]);
-// const retSetter: StateUpdater<number> = 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 (
+ <article>
+ <div class="payments">
+ <div class="tab">
+ <button
+ class={tab === "transactions" ? "tablinks active" : "tablinks"}
+ onClick={(): void => {
+ setTab("transactions");
+ }}
+ >
+ {i18n.str`Transactions`}
+ </button>
+ <button
+ class={tab === "cashouts" ? "tablinks active" : "tablinks"}
+ onClick={(): void => {
+ setTab("cashouts");
+ }}
+ >
+ {i18n.str`Cashouts`}
+ </button>
+ </div>
+ {tab === "transactions" && (
+ <div class="active">
+ <h3>{i18n.str`Latest transactions`}</h3>
+ <Transactions account={account} />
+ </div>
+ )}
+ {tab === "cashouts" && (
+ <div class="active">
+ <h3>{i18n.str`Latest cashouts`}</h3>
+ <Cashouts account={account} />
+ </div>
+ )}
+ </div>
+ </article>
+ );
+}