/* 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 } from "@gnu-taler/taler-util"; import { hooks } from "@gnu-taler/web-util/lib/index.browser"; import { h, Fragment, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; import useSWR, { SWRConfig, useSWRConfig } from "swr"; import { PageStateType, usePageContext } from "../../context/pageState.js"; import { useTranslationContext } from "../../context/translation.js"; import { useBackendState } from "../../hooks/backend.js"; import { bankUiSettings } from "../../settings.js"; import { getIbanFromPayto } from "../../utils.js"; import { BankFrame } from "./BankFrame.js"; import { LoginForm } from "./LoginForm.js"; import { PaymentOptions } from "./PaymentOptions.js"; import { TalerWithdrawalQRCode } from "./TalerWithdrawalQRCode.js"; import { Transactions } from "./Transactions.js"; export function AccountPage(): VNode { const [backendState, backendStateSetter] = useBackendState(); const { i18n } = useTranslationContext(); const { pageState, pageStateSetter } = usePageContext(); if (!pageState.isLoggedIn) { return (

{i18n.str`Welcome to ${bankUiSettings.bankName}!`}

); } if (typeof backendState === "undefined") { pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: false, error: { title: i18n.str`Page has a problem: logged in but backend state is lost.`, }, })); return

Error: waiting for details...

; } console.log("Showing the profile page.."); return ( ); } /** * Factor out login credentials. */ function SWRWithCredentials(props: any): VNode { const { username, password, backendUrl } = props; const headers = new Headers(); headers.append("Authorization", `Basic ${btoa(`${username}:${password}`)}`); console.log("Likely backend base URL", backendUrl); return ( { return fetch(backendUrl + url || "", { headers }).then((r) => { if (!r.ok) throw { status: r.status, json: r.json() }; return r.json(); }); }, }} > {props.children} ); } /** * 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(Props: any): VNode { const { cache } = useSWRConfig(); const { accountLabel, backendState } = Props; // 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 { pageState, pageStateSetter: setPageState } = usePageContext(); const { withdrawalInProgress, withdrawalId, isLoggedIn, 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") { console.log("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: { setPageState((prevState: PageStateType) => ({ ...prevState, isLoggedIn: false, 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: { setPageState((prevState: PageStateType) => ({ ...prevState, isLoggedIn: false, error: { title: i18n.str`Wrong credentials given.`, }, })); return

Wrong credentials...

; } default: { setPageState((prevState: PageStateType) => ({ ...prevState, isLoggedIn: false, 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. */ console.log(`maybe new withdrawal ${talerWithdrawUri}`); if (talerWithdrawUri) { console.log("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 = hooks.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]; }